From 2181a29be76813a97deed5ed5a4bed42afab8872 Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Sun, 30 Jun 2013 11:34:34 -0700 Subject: [PATCH 01/56] Starting work on select/focus logic. --- IPython/html/static/notebook/js/cell.js | 24 ++++++++++++++++++--- IPython/html/static/notebook/js/codecell.js | 13 +++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 502df103f..2a65e4fdd 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -39,6 +39,7 @@ var IPython = (function (IPython) { this.placeholder = options.placeholder || ''; this.read_only = options.cm_config.readOnly; this.selected = false; + this.focused = false; this.metadata = {}; // load this from metadata later ? this.user_highlight = 'auto'; @@ -136,7 +137,7 @@ var IPython = (function (IPython) { }; /** - * should be triggerd when cell is selected + * handle cell level logic when a cell is selected * @method select */ Cell.prototype.select = function () { @@ -144,9 +145,8 @@ var IPython = (function (IPython) { this.selected = true; }; - /** - * should be triggerd when cell is unselected + * handle cell level logic when a cell is unselected * @method unselect */ Cell.prototype.unselect = function () { @@ -154,6 +154,24 @@ var IPython = (function (IPython) { this.selected = false; }; + /** + * handle cell level logic when a cell is focused + * @method focus + */ + Cell.prototype.focus = function () { + this.element.addClass('focused'); + this.focused = true; + }; + + /** + * handle cell level logic when a cell is unfocused + * @method unfocus + */ + Cell.prototype.unfocus = function () { + this.element.removeClass('focused'); + this.focused = false; + }; + /** * should be overritten by subclass * @method get_text diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 132b3ab24..bd5c71151 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -306,13 +306,18 @@ var IPython = (function (IPython) { CodeCell.prototype.select = function () { IPython.Cell.prototype.select.apply(this); this.code_mirror.refresh(); - this.code_mirror.focus(); this.auto_highlight(); - // We used to need an additional refresh() after the focus, but - // it appears that this has been fixed in CM. This bug would show - // up on FF when a newly loaded markdown cell was edited. }; + CodeCell.prototype.focus = function () { + IPython.Cell.prototype.focus.apply(this); + this.code_mirror.focus(); + }; + + CodeCell.prototype.unfocus = function () { + IPython.Cell.prototype.focus.apply(this); + this.code_mirror.blur(); + }; CodeCell.prototype.select_all = function () { var start = {line: 0, ch: 0}; From 54088a010506f2e3f0ea8e097f3d7fbf489918a9 Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Wed, 3 Jul 2013 14:47:27 -0700 Subject: [PATCH 02/56] Adding new logic to cells. --- IPython/html/static/notebook/js/cell.js | 112 ++++++++++++--- IPython/html/static/notebook/js/codecell.js | 147 ++++++++++++-------- IPython/html/static/notebook/js/notebook.js | 12 +- IPython/html/static/notebook/js/textcell.js | 79 ++++++----- 4 files changed, 230 insertions(+), 120 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 2a65e4fdd..7d8506aa6 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -39,7 +39,8 @@ var IPython = (function (IPython) { this.placeholder = options.placeholder || ''; this.read_only = options.cm_config.readOnly; this.selected = false; - this.focused = false; + this.rendered = false; + this.mode = 'command'; this.metadata = {}; // load this from metadata later ? this.user_highlight = 'auto'; @@ -130,48 +131,125 @@ var IPython = (function (IPython) { * @method typeset */ Cell.prototype.typeset = function () { - if (window.MathJax){ + if (window.MathJax) { var cell_math = this.element.get(0); MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]); - } + }; }; /** * handle cell level logic when a cell is selected * @method select + * @return is the action being taken */ Cell.prototype.select = function () { - this.element.addClass('selected'); - this.selected = true; + if (!this.selected) { + this.element.addClass('selected'); + this.element.removeClass('unselected'); + this.selected = true; + return true; + } else { + return false; + }; }; /** * handle cell level logic when a cell is unselected * @method unselect + * @return is the action being taken */ Cell.prototype.unselect = function () { - this.element.removeClass('selected'); - this.selected = false; + if (this.selected) { + this.element.addClass('unselected'); + this.element.removeClass('selected'); + this.selected = false; + return true; + } else { + return false; + }; }; /** - * handle cell level logic when a cell is focused - * @method focus + * handle cell level logic when a cell is rendered + * @method render + * @return is the action being taken */ - Cell.prototype.focus = function () { - this.element.addClass('focused'); - this.focused = true; + Cell.prototype.render = function () { + if (!this.rendered) { + this.element.addClass('rendered'); + this.element.removeClass('unrendered'); + this.rendered = true; + return true; + } else { + return false; + }; }; /** - * handle cell level logic when a cell is unfocused - * @method unfocus + * handle cell level logic when a cell is unrendered + * @method unrender + * @return is the action being taken */ - Cell.prototype.unfocus = function () { - this.element.removeClass('focused'); - this.focused = false; + Cell.prototype.unrender = function () { + if (this.rendered) { + this.element.addClass('unrendered'); + this.element.removeClass('rendered'); + this.rendered = false; + return true; + } else { + return false; + }; }; + /** + * enter the command mode for the cell + * @method command_mode + * @return is the action being taken + */ + Cell.prototype.command_mode = function () { + if (this.mode !== 'command') { + this.element.addClass('command_mode'); + this.element.removeClass('edit_mode'); + this.mode = 'command'; + return true; + } else { + return false; + }; + }; + + /** + * enter the edit mode for the cell + * @method command_mode + * @return is the action being taken + */ + Cell.prototype.edit_mode = function () { + if (this.mode !== 'edit') { + this.element.addClass('edit_mode'); + this.element.removeClass('command_mode'); + this.mode = 'edit'; + return true; + } else { + return false; + }; + } + + /** + * Focus the cell in the DOM sense + * @method focus_cell + */ + Cell.prototype.focus_cell = function () { + this.element.focus(); + } + + /** + * Focus the editor area so a user can type + * @method focus_editor + */ + Cell.prototype.focus_editor = function () { + this.code_mirror.refresh(); + this.code_mirror.focus(); + } + /** * should be overritten by subclass * @method get_text diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index bd5c71151..9069279f9 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -149,53 +149,60 @@ var IPython = (function (IPython) { CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) { var that = this; - // whatever key is pressed, first, cancel the tooltip request before - // they are sent, and remove tooltip if any, except for tab again - if (event.type === 'keydown' && event.which != key.TAB ) { - IPython.tooltip.remove_and_cancel_tooltip(); - } - var cur = editor.getCursor(); - if (event.keyCode === key.ENTER){ - this.auto_highlight(); - } + if (this.mode === 'command') { + return false + } else if (this.mode === 'edit') { + // whatever key is pressed, first, cancel the tooltip request before + // they are sent, and remove tooltip if any, except for tab again + if (event.type === 'keydown' && event.which != key.TAB ) { + IPython.tooltip.remove_and_cancel_tooltip(); + }; + + var cur = editor.getCursor(); + if (event.keyCode === key.ENTER){ + this.auto_highlight(); + } - if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) { - // Always ignore shift-enter in CodeMirror as we handle it. - return true; - } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) { - // triger on keypress (!) otherwise inconsistent event.which depending on plateform - // browser and keyboard layout ! - // Pressing '(' , request tooltip, don't forget to reappend it - // The second argument says to hide the tooltip if the docstring - // is actually empty - IPython.tooltip.pending(that, true); - } else if (event.which === key.UPARROW && event.type === 'keydown') { - // If we are not at the top, let CM handle the up arrow and - // prevent the global keydown handler from handling it. - if (!that.at_top()) { - event.stop(); - return false; - } else { + if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) { + // Always ignore shift-enter in CodeMirror as we handle it. return true; - } - } else if (event.which === key.ESC) { - return IPython.tooltip.remove_and_cancel_tooltip(true); - } else if (event.which === key.DOWNARROW && event.type === 'keydown') { - // If we are not at the bottom, let CM handle the down arrow and - // prevent the global keydown handler from handling it. - if (!that.at_bottom()) { - event.stop(); - return false; - } else { + + } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) { + // triger on keypress (!) otherwise inconsistent event.which depending on plateform + // browser and keyboard layout ! + // Pressing '(' , request tooltip, don't forget to reappend it + // The second argument says to hide the tooltip if the docstring + // is actually empty + IPython.tooltip.pending(that, true); + } else if (event.which === key.UPARROW && event.type === 'keydown') { + // If we are not at the top, let CM handle the up arrow and + // prevent the global keydown handler from handling it. + if (!that.at_top()) { + event.stop(); + return false; + } else { + return true; + }; + } else if (event.which === key.ESC) { + IPython.tooltip.remove_and_cancel_tooltip(true); return true; - } - } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { - if (editor.somethingSelected()){ - var anchor = editor.getCursor("anchor"); - var head = editor.getCursor("head"); - if( anchor.line != head.line){ - return false; + } else if (event.which === key.DOWNARROW && event.type === 'keydown') { + // If we are not at the bottom, let CM handle the down arrow and + // prevent the global keydown handler from handling it. + if (!that.at_bottom()) { + event.stop(); + return false; + } else { + return true; + }; + } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { + if (editor.somethingSelected()){ + var anchor = editor.getCursor("anchor"); + var head = editor.getCursor("head"); + if( anchor.line != head.line){ + return false; + } } } IPython.tooltip.request(that); @@ -213,13 +220,10 @@ var IPython = (function (IPython) { // is empty. In this case, let CodeMirror handle indentation. return false; } else { - event.stop(); - this.completer.startCompletion(); - return true; - } - } else { - // keypress/keyup also trigger on TAB press, and we don't want to - // use those to disable tab completion. + // keypress/keyup also trigger on TAB press, and we don't want to + // use those to disable tab completion. + return false; + }; return false; } return false; @@ -304,21 +308,46 @@ var IPython = (function (IPython) { // Basic cell manipulation. CodeCell.prototype.select = function () { - IPython.Cell.prototype.select.apply(this); - this.code_mirror.refresh(); - this.auto_highlight(); + var continue = IPython.Cell.prototype.select.apply(this); + if (continue) { + this.code_mirror.refresh(); + this.auto_highlight(); + }; + return continue; }; - CodeCell.prototype.focus = function () { - IPython.Cell.prototype.focus.apply(this); - this.code_mirror.focus(); + CodeCell.prototype.render = function () { + var continue = IPython.Cell.prototype.render.apply(this); + if (continue) { + this.execute(); + }; + return continue; }; - CodeCell.prototype.unfocus = function () { - IPython.Cell.prototype.focus.apply(this); - this.code_mirror.blur(); + CodeCell.prototype.unrender = function () { + var continue = IPython.Cell.prototype.unrender.apply(this); + if (continue) { + this.clear_output(true, true, true); + }; + return continue; }; + CodeCell.prototype.command_mode = function () { + var continue = IPython.Cell.prototype.command_mode.apply(this); + if (continue) { + this.focus_cell(); + }; + return continue; + } + + CodeCell.prototype.edit_mode = function () { + var continue = IPython.Cell.prototype.edit_mode.apply(this); + if (continue) { + this.focus_editor(); + }; + return continue; + } + CodeCell.prototype.select_all = function () { var start = {line: 0, ch: 0}; var nlines = this.code_mirror.lineCount(); diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index e396b1207..3eebb2fcc 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -946,7 +946,7 @@ var IPython = (function (IPython) { text = ''; }; // The edit must come before the set_text. - target_cell.edit(); + target_cell.unrender(); target_cell.set_text(text); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell @@ -976,7 +976,7 @@ var IPython = (function (IPython) { text = ''; }; // The edit must come before the set_text. - target_cell.edit(); + target_cell.unrender(); target_cell.set_text(text); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell @@ -1011,7 +1011,7 @@ var IPython = (function (IPython) { }; // The edit must come before the set_text. target_cell.set_level(level); - target_cell.edit(); + target_cell.unrender(); target_cell.set_text(text); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell @@ -1179,7 +1179,7 @@ var IPython = (function (IPython) { cell.set_text(textb); cell.render(); var new_cell = this.insert_cell_above('markdown'); - new_cell.edit(); // editor must be visible to call set_text + new_cell.unrender(); // editor must be visible to call set_text new_cell.set_text(texta); new_cell.render(); this.select_next(); @@ -1208,7 +1208,7 @@ var IPython = (function (IPython) { if (cell instanceof IPython.CodeCell) { cell.set_text(upper_text+'\n'+text); } else if (cell instanceof IPython.MarkdownCell) { - cell.edit(); + cell.unrender(); cell.set_text(upper_text+'\n'+text); cell.render(); }; @@ -1238,7 +1238,7 @@ var IPython = (function (IPython) { if (cell instanceof IPython.CodeCell) { cell.set_text(text+'\n'+lower_text); } else if (cell instanceof IPython.MarkdownCell) { - cell.edit(); + cell.unrender(); cell.set_text(text+'\n'+lower_text); cell.render(); }; diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js index cf7f003d0..e8cbd35df 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -104,13 +104,13 @@ var IPython = (function (IPython) { this.element.keydown(function (event) { if (event.which === 13 && !event.shiftKey) { if (that.rendered) { - that.edit(); + that.unrender(); return false; }; }; }); this.element.dblclick(function () { - that.edit(); + that.unrender(); }); }; @@ -134,57 +134,59 @@ var IPython = (function (IPython) { return false; }; - /** - * Select the current cell and trigger 'focus' - * @method select - */ + // Cell level actions + TextCell.prototype.select = function () { - IPython.Cell.prototype.select.apply(this); - var output = this.element.find("div.text_cell_render"); - output.trigger('focus'); + var continue = IPython.Cell.prototype.select.apply(this); + if (continue) { + if (this.mode === 'edit') { + this.code_mirror.refresh(); + } + }; + return continue; }; - /** - * unselect the current cell and `render` it - * @method unselect - */ - TextCell.prototype.unselect = function() { - // render on selection of another cell - this.render(); - IPython.Cell.prototype.unselect.apply(this); + TextCell.prototype.render = function () { + var continue = IPython.Cell.prototype.render.apply(this); + if (continue) { + this.execute(); + }; + return continue; }; - /** - * - * put the current cell in edition mode - * @method edit - */ - TextCell.prototype.edit = function () { - if (this.rendered === true) { + TextCell.prototype.unrender = function () { + if (this.read_only) return; + var continue = IPython.Cell.prototype.unrender.apply(this); + if (continue) { var text_cell = this.element; var output = text_cell.find("div.text_cell_render"); output.hide(); text_cell.find('div.text_cell_input').show(); - this.code_mirror.refresh(); - this.code_mirror.focus(); - // We used to need an additional refresh() after the focus, but - // it appears that this has been fixed in CM. This bug would show - // up on FF when a newly loaded markdown cell was edited. - this.rendered = false; + this.focus_editor(); if (this.get_text() === this.placeholder) { this.set_text(''); this.refresh(); } - } - }; + }; + return continue; + }; - /** - * Empty, Subclasses must define render. - * @method render - */ - TextCell.prototype.render = function () {}; + TextCell.prototype.command_mode = function () { + var continue = IPython.Cell.prototype.command_mode.apply(this); + if (continue) { + this.focus_cell(); + }; + return continue; + } + TextCell.prototype.edit_mode = function () { + var continue = IPython.Cell.prototype.edit_mode.apply(this); + if (continue) { + this.focus_editor(); + }; + return continue; + } /** * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}} @@ -382,11 +384,12 @@ var IPython = (function (IPython) { /** @method render **/ RawCell.prototype.render = function () { + this.rendered = true; var text = this.get_text(); if (text === "") { text = this.placeholder; } - console.log('rendering', text); this.set_text(text); + this.unrender(); }; From 2a530ff30dba1c39265bfb1fc9ae11651fe5421d Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Mon, 8 Jul 2013 14:38:01 -0700 Subject: [PATCH 03/56] More work on the dual mode UX. --- IPython/html/static/notebook/js/cell.js | 51 +-- IPython/html/static/notebook/js/codecell.js | 49 ++- IPython/html/static/notebook/js/notebook.js | 463 +++++++++++--------- IPython/html/static/notebook/js/textcell.js | 206 ++++----- IPython/html/static/notebook/less/cell.less | 8 + IPython/html/static/style/ipython.min.css | 4 +- IPython/html/static/style/style.min.css | 4 +- 7 files changed, 439 insertions(+), 346 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 7d8506aa6..568eb1ead 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -123,7 +123,12 @@ var IPython = (function (IPython) { this.code_mirror.on("change", function(cm, change) { $([IPython.events]).trigger("set_dirty.Notebook", {value: true}); }); - } + }; + if (this.code_mirror) { + this.code_mirror.on('focus', function(cm, change) { + $([IPython.events]).trigger('edit_mode.Cell', {cell: that}); + }); + }; }; /** @@ -143,12 +148,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.select = function () { + console.log('Cell.select'); if (!this.selected) { this.element.addClass('selected'); this.element.removeClass('unselected'); this.selected = true; return true; } else { + console.log('WARNING: select'); return false; }; }; @@ -159,12 +166,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.unselect = function () { + console.log('Cell.unselect'); if (this.selected) { this.element.addClass('unselected'); this.element.removeClass('selected'); this.selected = false; return true; } else { + console.log('WARNING: unselect'); return false; }; }; @@ -175,12 +184,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.render = function () { + console.log('Cell.render'); if (!this.rendered) { this.element.addClass('rendered'); this.element.removeClass('unrendered'); this.rendered = true; return true; } else { + console.log('WARNING: render'); return false; }; }; @@ -191,12 +202,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.unrender = function () { + console.log('Cell.unrender'); if (this.rendered) { this.element.addClass('unrendered'); this.element.removeClass('rendered'); this.rendered = false; return true; } else { + console.log('WARNING: unrender'); return false; }; }; @@ -207,12 +220,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.command_mode = function () { + console.log('Cell.command_mode:', this.mode); if (this.mode !== 'command') { this.element.addClass('command_mode'); this.element.removeClass('edit_mode'); this.mode = 'command'; return true; } else { + console.log('WARNING: command_mode'); return false; }; }; @@ -223,12 +238,14 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.edit_mode = function () { + console.log('Cell.edit_mode:', this.mode); if (this.mode !== 'edit') { this.element.addClass('edit_mode'); this.element.removeClass('command_mode'); this.mode = 'edit'; return true; } else { + console.log('WARNING: edit_mode'); return false; }; } @@ -246,25 +263,10 @@ var IPython = (function (IPython) { * @method focus_editor */ Cell.prototype.focus_editor = function () { - this.code_mirror.refresh(); + this.refresh(); this.code_mirror.focus(); } - /** - * should be overritten by subclass - * @method get_text - */ - Cell.prototype.get_text = function () { - }; - - /** - * should be overritten by subclass - * @method set_text - * @param {string} text - */ - Cell.prototype.set_text = function (text) { - }; - /** * Refresh codemirror instance * @method refresh @@ -273,20 +275,19 @@ var IPython = (function (IPython) { this.code_mirror.refresh(); }; - /** * should be overritten by subclass - * @method edit - **/ - Cell.prototype.edit = function () { + * @method get_text + */ + Cell.prototype.get_text = function () { }; - /** * should be overritten by subclass - * @method render - **/ - Cell.prototype.render = function () { + * @method set_text + * @param {string} text + */ + Cell.prototype.set_text = function (text) { }; /** diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 9069279f9..9f20e2f01 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -139,6 +139,16 @@ var IPython = (function (IPython) { this.completer = new IPython.Completer(this); }; + /** @method bind_events */ + CodeCell.prototype.bind_events = function () { + IPython.Cell.prototype.bind_events.apply(this); + var that = this; + + this.element.focusout( + function() { that.auto_highlight(); } + ); + }; + /** * This method gets called in CodeMirror's onKeyDown/onKeyPress * handlers and is used to provide custom key handling. Its return @@ -308,44 +318,43 @@ var IPython = (function (IPython) { // Basic cell manipulation. CodeCell.prototype.select = function () { - var continue = IPython.Cell.prototype.select.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.select.apply(this); + console.log('CodeCell.select', cont); + if (cont) { this.code_mirror.refresh(); this.auto_highlight(); }; - return continue; + return cont; }; CodeCell.prototype.render = function () { - var continue = IPython.Cell.prototype.render.apply(this); - if (continue) { - this.execute(); - }; - return continue; + var cont = IPython.Cell.prototype.render.apply(this); + console.log('CodeCell.render'); + // Always execute, even if we are already in the rendered state + return cont; }; - + CodeCell.prototype.unrender = function () { - var continue = IPython.Cell.prototype.unrender.apply(this); - if (continue) { - this.clear_output(true, true, true); - }; - return continue; + // CodeCell is always rendered + return false; }; CodeCell.prototype.command_mode = function () { - var continue = IPython.Cell.prototype.command_mode.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.command_mode.apply(this); + console.log('CodeCell.command_mode'); + if (cont) { this.focus_cell(); }; - return continue; + return cont; } CodeCell.prototype.edit_mode = function () { - var continue = IPython.Cell.prototype.edit_mode.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.edit_mode.apply(this); + console.log('CodeCell.edit_mode'); + if (cont) { this.focus_editor(); }; - return continue; + return cont; } CodeCell.prototype.select_all = function () { diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 3eebb2fcc..d97a42627 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -39,6 +39,8 @@ var IPython = (function (IPython) { this.undelete_index = null; this.undelete_below = false; this.paste_enabled = false; + this.mode = 'command'; + this.edit_index = null; this.set_dirty(false); this.metadata = {}; this._checkpoint_after_save = false; @@ -74,7 +76,7 @@ var IPython = (function (IPython) { * @method baseProjectUrl * @return {String} The base project URL */ - Notebook.prototype.baseProjectUrl = function(){ + Notebook.prototype.baseProjectUrl = function() { return this._baseProjectUrl || $('body').data('baseProjectUrl'); }; @@ -131,6 +133,12 @@ var IPython = (function (IPython) { var index = that.find_cell_index(data.cell); that.select(index); }); + + $([IPython.events]).on('edit_mode.Cell', function (event, data) { + var index = that.find_cell_index(data.cell); + that.select(index); + that.edit_mode(); + }); $([IPython.events]).on('status_autorestarting.Kernel', function () { IPython.dialog.modal({ @@ -144,220 +152,222 @@ var IPython = (function (IPython) { }); }); - $(document).keydown(function (event) { - // Save (CTRL+S) or (AppleKey+S) - //metaKey = applekey on mac + // Event handlers for both command and edit mode if ((event.ctrlKey || event.metaKey) && event.keyCode==83) { + // Save (CTRL+S) or (Command+S on Mac) that.save_checkpoint(); event.preventDefault(); return false; } else if (event.which === key.ESC) { // Intercept escape at highest level to avoid closing // websocket connection with firefox - IPython.pager.collapse(); event.preventDefault(); + // Don't return yet to allow edit/command modes to handle } else if (event.which === key.SHIFT) { // ignore shift keydown return true; - } - if (event.which === key.UPARROW && !event.shiftKey) { - var cell = that.get_selected_cell(); - if (cell && cell.at_top()) { - event.preventDefault(); - that.select_prev(); - }; - } else if (event.which === key.DOWNARROW && !event.shiftKey) { - var cell = that.get_selected_cell(); - if (cell && cell.at_bottom()) { - event.preventDefault(); - that.select_next(); - }; } else if (event.which === key.ENTER && event.shiftKey) { - that.execute_selected_cell(); + that.execute_selected_cell('shift'); return false; } else if (event.which === key.ENTER && event.altKey) { // Execute code cell, and insert new in place - that.execute_selected_cell(); - // Only insert a new cell, if we ended up in an already populated cell - if (/\S/.test(that.get_selected_cell().get_text()) == true) { - that.insert_cell_above('code'); - } + that.execute_selected_cell('alt'); return false; } else if (event.which === key.ENTER && event.ctrlKey) { - that.execute_selected_cell({terminal:true}); - return false; - } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) { - that.control_key_active = true; - return false; - } else if (event.which === 88 && that.control_key_active) { - // Cut selected cell = x - that.cut_cell(); - that.control_key_active = false; - return false; - } else if (event.which === 67 && that.control_key_active) { - // Copy selected cell = c - that.copy_cell(); - that.control_key_active = false; - return false; - } else if (event.which === 86 && that.control_key_active) { - // Paste below selected cell = v - that.paste_cell_below(); - that.control_key_active = false; - return false; - } else if (event.which === 68 && that.control_key_active) { - // Delete selected cell = d - that.delete_cell(); - that.control_key_active = false; - return false; - } else if (event.which === 65 && that.control_key_active) { - // Insert code cell above selected = a - that.insert_cell_above('code'); - that.control_key_active = false; - return false; - } else if (event.which === 66 && that.control_key_active) { - // Insert code cell below selected = b - that.insert_cell_below('code'); - that.control_key_active = false; - return false; - } else if (event.which === 89 && that.control_key_active) { - // To code = y - that.to_code(); - that.control_key_active = false; - return false; - } else if (event.which === 77 && that.control_key_active) { - // To markdown = m - that.to_markdown(); - that.control_key_active = false; - return false; - } else if (event.which === 84 && that.control_key_active) { - // To Raw = t - that.to_raw(); - that.control_key_active = false; + that.execute_selected_cell('ctrl'); return false; - } else if (event.which === 49 && that.control_key_active) { - // To Heading 1 = 1 - that.to_heading(undefined, 1); - that.control_key_active = false; - return false; - } else if (event.which === 50 && that.control_key_active) { - // To Heading 2 = 2 - that.to_heading(undefined, 2); - that.control_key_active = false; - return false; - } else if (event.which === 51 && that.control_key_active) { - // To Heading 3 = 3 - that.to_heading(undefined, 3); - that.control_key_active = false; - return false; - } else if (event.which === 52 && that.control_key_active) { - // To Heading 4 = 4 - that.to_heading(undefined, 4); - that.control_key_active = false; - return false; - } else if (event.which === 53 && that.control_key_active) { - // To Heading 5 = 5 - that.to_heading(undefined, 5); - that.control_key_active = false; - return false; - } else if (event.which === 54 && that.control_key_active) { - // To Heading 6 = 6 - that.to_heading(undefined, 6); - that.control_key_active = false; - return false; - } else if (event.which === 79 && that.control_key_active) { - // Toggle output = o - if (event.shiftKey){ - that.toggle_output_scroll(); - } else { - that.toggle_output(); - } - that.control_key_active = false; - return false; - } else if (event.which === 83 && that.control_key_active) { - // Save notebook = s - that.save_checkpoint(); - that.control_key_active = false; - return false; - } else if (event.which === 74 && that.control_key_active) { - // Move cell down = j - that.move_cell_down(); - that.control_key_active = false; - return false; - } else if (event.which === 75 && that.control_key_active) { - // Move cell up = k - that.move_cell_up(); - that.control_key_active = false; - return false; - } else if (event.which === 80 && that.control_key_active) { - // Select previous = p - that.select_prev(); - that.control_key_active = false; - return false; - } else if (event.which === 78 && that.control_key_active) { - // Select next = n - that.select_next(); - that.control_key_active = false; - return false; - } else if (event.which === 76 && that.control_key_active) { - // Toggle line numbers = l - that.cell_toggle_line_numbers(); - that.control_key_active = false; - return false; - } else if (event.which === 73 && that.control_key_active) { - // Interrupt kernel = i - that.session.interrupt_kernel(); - that.control_key_active = false; - return false; - } else if (event.which === 190 && that.control_key_active) { - // Restart kernel = . # matches qt console - that.restart_kernel(); - that.control_key_active = false; - return false; - } else if (event.which === 72 && that.control_key_active) { - // Show keyboard shortcuts = h - IPython.quick_help.show_keyboard_shortcuts(); - that.control_key_active = false; - return false; - } else if (event.which === 90 && that.control_key_active) { - // Undo last cell delete = z - that.undelete(); - that.control_key_active = false; - return false; - } else if ((event.which === 189 || event.which === 173) && - that.control_key_active) { - // how fun! '-' is 189 in Chrome, but 173 in FF and Opera - // Split cell = - - that.split_cell(); - that.control_key_active = false; - return false; - } else if (that.control_key_active) { - that.control_key_active = false; - return true; } + + // Event handlers for edit mode + if (that.mode === 'edit') { + if (event.which === key.ESC) { + // ESC + that.command_mode(); + return false; + } else if (event.which === 77 && event.ctrlKey) { + // Ctrl-m + that.command_mode(); + return false; + } else if (event.which === key.UPARROW && !event.shiftKey) { + var cell = that.get_selected_cell(); + if (cell && cell.at_top()) { + event.preventDefault(); + that.command_mode() + that.select_prev(); + that.edit_mode(); + return false; + }; + } else if (event.which === key.DOWNARROW && !event.shiftKey) { + var cell = that.get_selected_cell(); + if (cell && cell.at_bottom()) { + event.preventDefault(); + that.command_mode() + that.select_next(); + that.edit_mode(); + return false; + }; + }; + // Event handlers for command mode + } else if (that.mode === 'command' && !(event.ctrlKey || event.altKey)) { + if (event.which === key.ENTER && !(event.ctrlKey || event.altKey || event.shiftKey)) { + // Enter edit mode = ENTER alone + that.edit_mode(); + return false + } else if (event.which === key.UPARROW && !event.shiftKey) { + var index = that.get_selected_index(); + if (index !== 0 && index !== null) { + that.select_prev(); + var cell = that.get_selected_cell(); + cell.focus_cell(); + }; + return false; + } else if (event.which === key.DOWNARROW && !event.shiftKey) { + var index = that.get_selected_index(); + if (index !== (that.ncells()-1) && index !== null) { + that.select_next(); + var cell = that.get_selected_cell(); + cell.focus_cell(); + }; + return false; + } else if (event.which === 88) { + // Cut selected cell = x + that.cut_cell(); + return false; + } else if (event.which === 67) { + // Copy selected cell = c + that.copy_cell(); + return false; + } else if (event.which === 86) { + // Paste below selected cell = v + that.paste_cell_below(); + return false; + } else if (event.which === 68) { + // Delete selected cell = d + that.delete_cell(); + return false; + } else if (event.which === 65) { + // Insert code cell above selected = a + that.insert_cell_above('code'); + that.select_prev(); + return false; + } else if (event.which === 66) { + // Insert code cell below selected = b + that.insert_cell_below('code'); + that.select_next(); + return false; + } else if (event.which === 89) { + // To code = y + that.to_code(); + return false; + } else if (event.which === 77) { + // To markdown = m + that.to_markdown(); + return false; + } else if (event.which === 84) { + // To Raw = t + that.to_raw(); + return false; + } else if (event.which === 49) { + // To Heading 1 = 1 + that.to_heading(undefined, 1); + return false; + } else if (event.which === 50) { + // To Heading 2 = 2 + that.to_heading(undefined, 2); + return false; + } else if (event.which === 51) { + // To Heading 3 = 3 + that.to_heading(undefined, 3); + return false; + } else if (event.which === 52) { + // To Heading 4 = 4 + that.to_heading(undefined, 4); + return false; + } else if (event.which === 53) { + // To Heading 5 = 5 + that.to_heading(undefined, 5); + return false; + } else if (event.which === 54) { + // To Heading 6 = 6 + that.to_heading(undefined, 6); + return false; + } else if (event.which === 79) { + // Toggle output = o + if (event.shiftKey) { + that.toggle_output_scroll(); + } else { + that.toggle_output(); + }; + return false; + } else if (event.which === 83) { + // Save notebook = s + that.save_checkpoint(); + that.control_key_active = false; + return false; + } else if (event.which === 74) { + // Move cell down = j + that.move_cell_down(); + return false; + } else if (event.which === 75) { + // Move cell up = k + that.move_cell_up(); + return false; + } else if (event.which === 80) { + // Select previous = p + that.select_prev(); + return false; + } else if (event.which === 78) { + // Select next = n + that.select_next(); + return false; + } else if (event.which === 76) { + // Toggle line numbers = l + that.cell_toggle_line_numbers(); + return false; + } else if (event.which === 73) { + // Interrupt kernel = i + that.kernel.interrupt(); + return false; + } else if (event.which === 190) { + // Restart kernel = . # matches qt console + that.restart_kernel(); + return false; + } else if (event.which === 72) { + // Show keyboard shortcuts = h + IPython.quick_help.show_keyboard_shortcuts(); + return false; + } else if (event.which === 90) { + // Undo last cell delete = z + that.undelete(); + return false; + }; + }; + + // If we havn't handled it, let someone else. return true; }); - var collapse_time = function(time){ + var collapse_time = function (time) { var app_height = $('#ipython-main-app').height(); // content height var splitter_height = $('div#pager_splitter').outerHeight(true); var new_height = app_height - splitter_height; that.element.animate({height : new_height + 'px'}, time); - } + }; - this.element.bind('collapse_pager', function (event,extrap) { + this.element.bind('collapse_pager', function (event, extrap) { var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast'; collapse_time(time); }); - var expand_time = function(time) { + var expand_time = function (time) { var app_height = $('#ipython-main-app').height(); // content height var splitter_height = $('div#pager_splitter').outerHeight(true); var pager_height = $('div#pager').outerHeight(true); var new_height = app_height - pager_height - splitter_height; that.element.animate({height : new_height + 'px'}, time); - } + }; this.element.bind('expand_pager', function (event, extrap) { var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast'; @@ -653,6 +663,7 @@ var IPython = (function (IPython) { this.get_cell(sindex).unselect(); }; var cell = this.get_cell(index); + console.log('Notebook.select', index); cell.select(); if (cell.cell_type === 'heading') { $([IPython.events]).trigger('selected_cell_type_changed.Notebook', @@ -692,6 +703,48 @@ var IPython = (function (IPython) { }; + // Edit/Command mode + + /** + * Enter command mode for the currently selected cell + * + * @method command_mode + */ + Notebook.prototype.command_mode = function () { + console.log('Notebook.command_mode', this.mode, this.edit_index); + if (this.mode !== 'command') { + var cell = this.get_cell(this.edit_index); + if (cell) { + cell.command_mode(); + this.mode = 'command'; + this.edit_index = null; + }; + }; + }; + + /** + * Enter edit mode for the currently selected cell + * + * @method editmode + */ + Notebook.prototype.edit_mode = function () { + var index = this.get_selected_index(); + console.log('Notebook.edit_mode', this.mode, index); + if (index !== this.edit_index) { + if (this.edit_index !== null) { + var old_cell = this.get_cell(this.edit_index) + old_cell.command_mode(); + } + var cell = this.get_cell(index); + if (cell) { + cell.edit_mode(); + this.mode = 'edit'; + this.edit_index = index; + }; + }; + }; + + // Cell movement /** @@ -804,10 +857,12 @@ var IPython = (function (IPython) { cell = new IPython.HeadingCell(); } - if(this._insert_element_at_index(cell.element,index)){ + if(this._insert_element_at_index(cell.element,index)) { cell.render(); - this.select(this.find_cell_index(cell)); $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index}); + cell.refresh(); + // TODO: should we really get rid of this? + //this.select(this.find_cell_index(cell)); this.set_dirty(true); } } @@ -952,6 +1007,8 @@ var IPython = (function (IPython) { // to this state, instead of a blank cell target_cell.code_mirror.clearHistory(); source_element.remove(); + this.select(i); + this.edit_mode(); this.set_dirty(true); }; }; @@ -1440,31 +1497,41 @@ var IPython = (function (IPython) { * @method execute_selected_cell * @param {Object} options Customize post-execution behavior */ - Notebook.prototype.execute_selected_cell = function (options) { - // add_new: should a new cell be added if we are at the end of the nb - // terminal: execute in terminal mode, which stays in the current cell - var default_options = {terminal: false, add_new: true}; - $.extend(default_options, options); + Notebook.prototype.execute_selected_cell = function (mode) { + // mode = shift, ctrl, alt + mode = mode || 'shift' var that = this; var cell = that.get_selected_cell(); var cell_index = that.find_cell_index(cell); - if (cell instanceof IPython.CodeCell) { - cell.execute(); - } - if (default_options.terminal) { - cell.select_all(); - } else { - if ((cell_index === (that.ncells()-1)) && default_options.add_new) { + + cell.execute(); + console.log('Notebook.execute_selected_cell', mode); + if (mode === 'shift') { + if (cell_index === (that.ncells()-1)) { that.insert_cell_below('code'); - // If we are adding a new cell at the end, scroll down to show it. + that.select(cell_index+1); + that.edit_mode(); that.scroll_to_bottom(); } else { - that.select(cell_index+1); - }; - }; + that.command_mode(); + } + } else if (mode === 'ctrl') { + that.select(cell_index+1); + that.get_cell(cell_index+1).focus_cell(); + } else if (mode === 'alt') { + // Only insert a new cell, if we ended up in an already populated cell + if (/\S/.test(that.get_next_cell().get_text()) == true) { + that.insert_cell_below('code'); + } + var next_index = cell_index+1; + that.select(cell_index+1); + that.edit_mode(); + } + this.set_dirty(true); }; + /** * Execute all cells below the selected cell. * @@ -1909,9 +1976,13 @@ var IPython = (function (IPython) { this.fromJSON(data); if (this.ncells() === 0) { this.insert_cell_below('code'); + this.select(0); + this.edit_mode(); + } else { + this.select(0); + this.command_mode(); }; this.set_dirty(false); - this.select(0); this.scroll_to_top(); if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) { var msg = "This notebook has been converted from an older " + diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js index e8cbd35df..3c50fde59 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -64,7 +64,6 @@ var IPython = (function (IPython) { }; - /** * Create the DOM element of the TextCell * @method create_element @@ -101,6 +100,8 @@ var IPython = (function (IPython) { TextCell.prototype.bind_events = function () { IPython.Cell.prototype.bind_events.apply(this); var that = this; + + // TODO: move this to the notebook event handler this.element.keydown(function (event) { if (event.which === 13 && !event.shiftKey) { if (that.rendered) { @@ -109,11 +110,13 @@ var IPython = (function (IPython) { }; }; }); + this.element.dblclick(function () { that.unrender(); }); }; + /** * This method gets called in CodeMirror's onKeyDown/onKeyPress * handlers and is used to provide custom key handling. @@ -126,38 +129,34 @@ var IPython = (function (IPython) { * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise */ TextCell.prototype.handle_codemirror_keyevent = function (editor, event) { - - if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) { - // Always ignore shift-enter in CodeMirror as we handle it. - return true; - } + if (this.mode === 'command') { + return false + } else if (this.mode === 'edit') { + if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) { + // Always ignore shift-enter in CodeMirror as we handle it. + return true; + }; + return false; + }; return false; }; // Cell level actions TextCell.prototype.select = function () { - var continue = IPython.Cell.prototype.select.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.select.apply(this); + if (cont) { if (this.mode === 'edit') { this.code_mirror.refresh(); } }; - return continue; - }; - - TextCell.prototype.render = function () { - var continue = IPython.Cell.prototype.render.apply(this); - if (continue) { - this.execute(); - }; - return continue; + return cont; }; TextCell.prototype.unrender = function () { if (this.read_only) return; - var continue = IPython.Cell.prototype.unrender.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.unrender.apply(this); + if (cont) { var text_cell = this.element; var output = text_cell.find("div.text_cell_render"); output.hide(); @@ -169,23 +168,27 @@ var IPython = (function (IPython) { } }; - return continue; + return cont; + }; + + TextCell.prototype.execute = function () { + this.render(); }; TextCell.prototype.command_mode = function () { - var continue = IPython.Cell.prototype.command_mode.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.command_mode.apply(this); + if (cont) { this.focus_cell(); }; - return continue; + return cont; } TextCell.prototype.edit_mode = function () { - var continue = IPython.Cell.prototype.edit_mode.apply(this); - if (continue) { + var cont = IPython.Cell.prototype.edit_mode.apply(this); + if (cont) { this.focus_editor(); }; - return continue; + return cont; } /** @@ -224,36 +227,51 @@ var IPython = (function (IPython) { }; /** - * not deprecated, but implementation wrong * @method at_top - * @deprecated - * @return {Boolean} true is cell rendered, false otherwise - * I doubt this is what it is supposed to do - * this implementation is completly false + * @return {Boolean} */ TextCell.prototype.at_top = function () { if (this.rendered) { return true; } else { - return false; - } + if (cursor.line === 0 && cursor.ch === 0) { + return true; + } else { + return false; + }; + }; }; + /** @method at_bottom **/ + TextCell.prototype.at_bottom = function () { + if (this.rendered) { + return true + } else { + var cursor = this.code_mirror.getCursor(); + if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) { + return true; + } else { + return false; + }; + }; + }; + /** - * not deprecated, but implementation wrong * @method at_bottom - * @deprecated - * @return {Boolean} true is cell rendered, false otherwise - * I doubt this is what it is supposed to do - * this implementation is completly false + * @return {Boolean} * */ TextCell.prototype.at_bottom = function () { if (this.rendered) { return true; } else { - return false; - } + var cursor = this.code_mirror.getCursor(); + if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) { + return true; + } else { + return false; + }; + }; }; /** @@ -308,16 +326,14 @@ var IPython = (function (IPython) { placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$" } - - - MarkdownCell.prototype = new TextCell(); /** * @method render */ MarkdownCell.prototype.render = function () { - if (this.rendered === false) { + var cont = IPython.TextCell.prototype.render.apply(this); + if (cont) { var text = this.get_text(); var math = null; if (text === "") { text = this.placeholder; } @@ -339,9 +355,9 @@ var IPython = (function (IPython) { } this.element.find('div.text_cell_input').hide(); this.element.find("div.text_cell_render").show(); - this.typeset(); - this.rendered = true; - } + this.typeset() + }; + return cont; }; @@ -357,11 +373,6 @@ var IPython = (function (IPython) { this.cell_type = 'raw'; TextCell.apply(this, [options]); - - var that = this; - this.element.focusout( - function() { that.auto_highlight(); } - ); }; RawCell.options_default = { @@ -370,10 +381,17 @@ var IPython = (function (IPython) { "When passing through nbconvert, a Raw Cell's content is added to the output unmodified." }; - - RawCell.prototype = new TextCell(); + /** @method bind_events **/ + RawCell.prototype.bind_events = function () { + TextCell.prototype.bind_events.apply(this); + var that = this + this.element.focusout(function() { + that.auto_highlight(); + }); + }; + /** * Trigger autodetection of highlight scheme for current cell * @method auto_highlight @@ -384,12 +402,13 @@ var IPython = (function (IPython) { /** @method render **/ RawCell.prototype.render = function () { - - this.rendered = true; + // Make sure that this cell type can never be rendered + if (this.rendered) { + this.unrender(); + } var text = this.get_text(); if (text === "") { text = this.placeholder; } this.set_text(text); - this.unrender(); }; @@ -397,55 +416,34 @@ var IPython = (function (IPython) { RawCell.prototype.handle_codemirror_keyevent = function (editor, event) { var that = this; - if (event.which === key.UPARROW && event.type === 'keydown') { - // If we are not at the top, let CM handle the up arrow and - // prevent the global keydown handler from handling it. - if (!that.at_top()) { - event.stop(); - return false; - } else { - return true; - }; - } else if (event.which === key.DOWNARROW && event.type === 'keydown') { - // If we are not at the bottom, let CM handle the down arrow and - // prevent the global keydown handler from handling it. - if (!that.at_bottom()) { - event.stop(); - return false; - } else { - return true; + if (this.mode === 'command') { + return false + } else if (this.mode === 'edit') { + // TODO: review these handlers... + if (event.which === key.UPARROW && event.type === 'keydown') { + // If we are not at the top, let CM handle the up arrow and + // prevent the global keydown handler from handling it. + if (!that.at_top()) { + event.stop(); + return false; + } else { + return true; + }; + } else if (event.which === key.DOWNARROW && event.type === 'keydown') { + // If we are not at the bottom, let CM handle the down arrow and + // prevent the global keydown handler from handling it. + if (!that.at_bottom()) { + event.stop(); + return false; + } else { + return true; + }; }; + return false; }; return false; }; - /** @method select **/ - RawCell.prototype.select = function () { - IPython.Cell.prototype.select.apply(this); - this.edit(); - }; - - /** @method at_top **/ - RawCell.prototype.at_top = function () { - var cursor = this.code_mirror.getCursor(); - if (cursor.line === 0 && cursor.ch === 0) { - return true; - } else { - return false; - } - }; - - - /** @method at_bottom **/ - RawCell.prototype.at_bottom = function () { - var cursor = this.code_mirror.getCursor(); - if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) { - return true; - } else { - return false; - } - }; - /** * @class HeadingCell @@ -541,7 +539,8 @@ var IPython = (function (IPython) { HeadingCell.prototype.render = function () { - if (this.rendered === false) { + var cont = IPython.TextCell.prototype.render.apply(this); + if (cont) { var text = this.get_text(); var math = null; // Markdown headings must be a single line @@ -567,8 +566,9 @@ var IPython = (function (IPython) { this.typeset(); this.element.find('div.text_cell_input').hide(); this.element.find("div.text_cell_render").show(); - this.rendered = true; + }; + return cont; }; IPython.TextCell = TextCell; diff --git a/IPython/html/static/notebook/less/cell.less b/IPython/html/static/notebook/less/cell.less index 0cf8078c3..ed279f480 100644 --- a/IPython/html/static/notebook/less/cell.less +++ b/IPython/html/static/notebook/less/cell.less @@ -6,6 +6,14 @@ div.cell { .corner-all; border : thin @border_color solid; } + + &.edit_mode { + .corner-all; + border : thin green solid; + } +} + +div.cell { width: 100%; padding: 5px 5px 5px 0px; /* This acts as a spacer between cells, that is outside the border */ diff --git a/IPython/html/static/style/ipython.min.css b/IPython/html/static/style/ipython.min.css index a26bd1f65..7d7e79040 100644 --- a/IPython/html/static/style/ipython.min.css +++ b/IPython/html/static/style/ipython.min.css @@ -54,7 +54,9 @@ input.engine_num_input{height:20px;margin-bottom:2px;padding-top:0;padding-botto .ansibgpurple{background-color:magenta;} .ansibgcyan{background-color:cyan;} .ansibggray{background-color:gray;} -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;} +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;} +div.cell.edit_mode{border-radius:4px;border:thin green solid;} +div.cell{width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;} div.prompt{min-width:11ex;padding:0.4em;margin:0px;font-family:monospace;text-align:right;line-height:1.231em;} 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;} div.prompt:empty{padding-top:0;padding-bottom:0;} diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 19f14f54d..88d3332db 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -1435,7 +1435,9 @@ input.engine_num_input{height:20px;margin-bottom:2px;padding-top:0;padding-botto .ansibgpurple{background-color:magenta;} .ansibgcyan{background-color:cyan;} .ansibggray{background-color:gray;} -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;} +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;} +div.cell.edit_mode{border-radius:4px;border:thin green solid;} +div.cell{width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;} div.prompt{min-width:11ex;padding:0.4em;margin:0px;font-family:monospace;text-align:right;line-height:1.231em;} 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;} div.prompt:empty{padding-top:0;padding-bottom:0;} From 40977e545ba746c7aacbbd2ce560b22aecf5deaa Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Wed, 10 Jul 2013 17:14:30 -0700 Subject: [PATCH 04/56] Semi working version of basic dual mode UX. As of this point there are lots of things that don't work, but most of the basic dual mode interactions do work fine. --- IPython/html/static/notebook/js/cell.js | 21 ++-- IPython/html/static/notebook/js/codecell.js | 7 +- IPython/html/static/notebook/js/notebook.js | 102 ++++++++++---------- IPython/html/static/notebook/js/textcell.js | 48 ++++++--- 4 files changed, 93 insertions(+), 85 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 568eb1ead..fb1e971c0 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -112,12 +112,17 @@ var IPython = (function (IPython) { that.element.click(function (event) { if (that.selected === false) { $([IPython.events]).trigger('select.Cell', {'cell':that}); - } + }; }); that.element.focusin(function (event) { if (that.selected === false) { $([IPython.events]).trigger('select.Cell', {'cell':that}); - } + }; + }); + that.element.focusout(function (event) { + if (that.mode === 'edit') { + $([IPython.events]).trigger('command_mode.Cell', {'cell':that}); + }; }); if (this.code_mirror) { this.code_mirror.on("change", function(cm, change) { @@ -148,14 +153,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.select = function () { - console.log('Cell.select'); if (!this.selected) { this.element.addClass('selected'); this.element.removeClass('unselected'); this.selected = true; return true; } else { - console.log('WARNING: select'); return false; }; }; @@ -166,14 +169,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.unselect = function () { - console.log('Cell.unselect'); if (this.selected) { this.element.addClass('unselected'); this.element.removeClass('selected'); this.selected = false; return true; } else { - console.log('WARNING: unselect'); return false; }; }; @@ -184,14 +185,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.render = function () { - console.log('Cell.render'); if (!this.rendered) { this.element.addClass('rendered'); this.element.removeClass('unrendered'); this.rendered = true; return true; } else { - console.log('WARNING: render'); return false; }; }; @@ -202,14 +201,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.unrender = function () { - console.log('Cell.unrender'); if (this.rendered) { this.element.addClass('unrendered'); this.element.removeClass('rendered'); this.rendered = false; return true; } else { - console.log('WARNING: unrender'); return false; }; }; @@ -220,14 +217,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.command_mode = function () { - console.log('Cell.command_mode:', this.mode); if (this.mode !== 'command') { this.element.addClass('command_mode'); this.element.removeClass('edit_mode'); this.mode = 'command'; return true; } else { - console.log('WARNING: command_mode'); return false; }; }; @@ -238,14 +233,12 @@ var IPython = (function (IPython) { * @return is the action being taken */ Cell.prototype.edit_mode = function () { - console.log('Cell.edit_mode:', this.mode); if (this.mode !== 'edit') { this.element.addClass('edit_mode'); this.element.removeClass('command_mode'); this.mode = 'edit'; return true; } else { - console.log('WARNING: edit_mode'); return false; }; } diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 9f20e2f01..164c72da2 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -157,7 +157,6 @@ var IPython = (function (IPython) { * @method handle_codemirror_keyevent */ CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) { - var that = this; if (this.mode === 'command') { @@ -174,7 +173,7 @@ var IPython = (function (IPython) { this.auto_highlight(); } - if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) { + if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) { // Always ignore shift-enter in CodeMirror as we handle it. return true; @@ -319,7 +318,6 @@ var IPython = (function (IPython) { CodeCell.prototype.select = function () { var cont = IPython.Cell.prototype.select.apply(this); - console.log('CodeCell.select', cont); if (cont) { this.code_mirror.refresh(); this.auto_highlight(); @@ -329,7 +327,6 @@ var IPython = (function (IPython) { CodeCell.prototype.render = function () { var cont = IPython.Cell.prototype.render.apply(this); - console.log('CodeCell.render'); // Always execute, even if we are already in the rendered state return cont; }; @@ -341,7 +338,6 @@ var IPython = (function (IPython) { CodeCell.prototype.command_mode = function () { var cont = IPython.Cell.prototype.command_mode.apply(this); - console.log('CodeCell.command_mode'); if (cont) { this.focus_cell(); }; @@ -350,7 +346,6 @@ var IPython = (function (IPython) { CodeCell.prototype.edit_mode = function () { var cont = IPython.Cell.prototype.edit_mode.apply(this); - console.log('CodeCell.edit_mode'); if (cont) { this.focus_editor(); }; diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index d97a42627..0905c1543 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -40,7 +40,6 @@ var IPython = (function (IPython) { this.undelete_below = false; this.paste_enabled = false; this.mode = 'command'; - this.edit_index = null; this.set_dirty(false); this.metadata = {}; this._checkpoint_after_save = false; @@ -139,7 +138,11 @@ var IPython = (function (IPython) { that.select(index); that.edit_mode(); }); - + + $([IPython.events]).on('command_mode.Cell', function (event, data) { + that.command_mode(); + }); + $([IPython.events]).on('status_autorestarting.Kernel', function () { IPython.dialog.modal({ title: "Kernel Restarting", @@ -660,10 +663,10 @@ var IPython = (function (IPython) { if (this.is_valid_cell_index(index)) { var sindex = this.get_selected_index() if (sindex !== null && index !== sindex) { + this.command_mode(); this.get_cell(sindex).unselect(); }; var cell = this.get_cell(index); - console.log('Notebook.select', index); cell.select(); if (cell.cell_type === 'heading') { $([IPython.events]).trigger('selected_cell_type_changed.Notebook', @@ -705,41 +708,36 @@ var IPython = (function (IPython) { // Edit/Command mode - /** - * Enter command mode for the currently selected cell - * - * @method command_mode - */ + Notebook.prototype.get_edit_index = function () { + var result = null; + this.get_cell_elements().filter(function (index) { + if ($(this).data("cell").mode === 'edit') { + result = index; + }; + }); + return result; + }; + Notebook.prototype.command_mode = function () { - console.log('Notebook.command_mode', this.mode, this.edit_index); if (this.mode !== 'command') { - var cell = this.get_cell(this.edit_index); + var index = this.get_edit_index(); + var cell = this.get_cell(index); if (cell) { cell.command_mode(); this.mode = 'command'; - this.edit_index = null; }; }; }; - /** - * Enter edit mode for the currently selected cell - * - * @method editmode - */ Notebook.prototype.edit_mode = function () { - var index = this.get_selected_index(); - console.log('Notebook.edit_mode', this.mode, index); - if (index !== this.edit_index) { - if (this.edit_index !== null) { - var old_cell = this.get_cell(this.edit_index) - old_cell.command_mode(); - } + if (this.mode !== 'edit') { + // We are in command mode so get_edit_index() is null!!! + var index = this.get_selected_index(); + if (index === null) {return;} // No cell is selected var cell = this.get_cell(index); if (cell) { cell.edit_mode(); this.mode = 'edit'; - this.edit_index = index; }; }; }; @@ -763,6 +761,7 @@ var IPython = (function (IPython) { tomove.detach(); pivot.before(tomove); this.select(i-1); + }; this.set_dirty(true); }; @@ -779,7 +778,7 @@ var IPython = (function (IPython) { **/ Notebook.prototype.move_cell_down = function (index) { var i = this.index_or_selected(index); - if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) { + if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) { var pivot = this.get_cell_element(i+1); var tomove = this.get_cell_element(i); if (pivot !== null && tomove !== null) { @@ -1000,7 +999,7 @@ var IPython = (function (IPython) { if (text === source_cell.placeholder) { text = ''; }; - // The edit must come before the set_text. + // We must show the editor before setting its contents target_cell.unrender(); target_cell.set_text(text); // make this value the starting point, so that we can only undo @@ -1032,13 +1031,15 @@ var IPython = (function (IPython) { if (text === source_cell.placeholder) { text = ''; }; - // The edit must come before the set_text. + // We must show the editor before setting its contents target_cell.unrender(); target_cell.set_text(text); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell target_cell.code_mirror.clearHistory(); source_element.remove(); + this.select(i); + this.edit_mode(); this.set_dirty(true); }; }; @@ -1066,7 +1067,7 @@ var IPython = (function (IPython) { if (text === source_cell.placeholder) { text = ''; }; - // The edit must come before the set_text. + // We must show the editor before setting its contents target_cell.set_level(level); target_cell.unrender(); target_cell.set_text(text); @@ -1074,6 +1075,8 @@ var IPython = (function (IPython) { // to this state, instead of a blank cell target_cell.code_mirror.clearHistory(); source_element.remove(); + this.select(i); + this.edit_mode(); this.set_dirty(true); }; $([IPython.events]).trigger('selected_cell_type_changed.Notebook', @@ -1500,34 +1503,35 @@ var IPython = (function (IPython) { Notebook.prototype.execute_selected_cell = function (mode) { // mode = shift, ctrl, alt mode = mode || 'shift' - var that = this; - var cell = that.get_selected_cell(); - var cell_index = that.find_cell_index(cell); + var cell = this.get_selected_cell(); + var cell_index = this.find_cell_index(cell); cell.execute(); - console.log('Notebook.execute_selected_cell', mode); + + // If we are at the end always insert a new cell and return + if (cell_index === (this.ncells()-1)) { + this.insert_cell_below('code'); + this.select(cell_index+1); + this.edit_mode(); + this.scroll_to_bottom(); + this.set_dirty(true); + return; + } + if (mode === 'shift') { - if (cell_index === (that.ncells()-1)) { - that.insert_cell_below('code'); - that.select(cell_index+1); - that.edit_mode(); - that.scroll_to_bottom(); - } else { - that.command_mode(); - } + this.command_mode(); } else if (mode === 'ctrl') { - that.select(cell_index+1); - that.get_cell(cell_index+1).focus_cell(); + this.select(cell_index+1); + this.get_cell(cell_index+1).focus_cell(); } else if (mode === 'alt') { // Only insert a new cell, if we ended up in an already populated cell - if (/\S/.test(that.get_next_cell().get_text()) == true) { - that.insert_cell_below('code'); + var next_text = this.get_cell(cell_index+1).get_text(); + if (/\S/.test(next_text) === true) { + this.insert_cell_below('code'); } - var next_index = cell_index+1; - that.select(cell_index+1); - that.edit_mode(); + this.select(cell_index+1); + this.edit_mode(); } - this.set_dirty(true); }; @@ -1651,7 +1655,7 @@ var IPython = (function (IPython) { cell_data.cell_type = 'raw'; } - new_cell = this.insert_cell_at_bottom(cell_data.cell_type); + new_cell = this.insert_cell_at_index(cell_data.cell_type, i); new_cell.fromJSON(cell_data); }; }; diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js index 3c50fde59..5bc05f2a2 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -101,18 +101,11 @@ var IPython = (function (IPython) { IPython.Cell.prototype.bind_events.apply(this); var that = this; - // TODO: move this to the notebook event handler - this.element.keydown(function (event) { - if (event.which === 13 && !event.shiftKey) { - if (that.rendered) { - that.unrender(); - return false; - }; - }; - }); - this.element.dblclick(function () { - that.unrender(); + if (that.selected === false) { + $([IPython.events]).trigger('select.Cell', {'cell':that}); + }; + $([IPython.events]).trigger('edit_mode.Cell', {cell: that}); }); }; @@ -129,13 +122,32 @@ var IPython = (function (IPython) { * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise */ TextCell.prototype.handle_codemirror_keyevent = function (editor, event) { + var that = this; if (this.mode === 'command') { return false } else if (this.mode === 'edit') { - if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) { + if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) { // Always ignore shift-enter in CodeMirror as we handle it. return true; - }; + } else if (event.which === key.UPARROW && event.type === 'keydown') { + // If we are not at the top, let CM handle the up arrow and + // prevent the global keydown handler from handling it. + if (!that.at_top()) { + event.stop(); + return false; + } else { + return true; + }; + } else if (event.which === key.DOWNARROW && event.type === 'keydown') { + // If we are not at the bottom, let CM handle the down arrow and + // prevent the global keydown handler from handling it. + if (!that.at_bottom()) { + event.stop(); + return false; + } else { + return true; + }; + } return false; }; return false; @@ -186,6 +198,7 @@ var IPython = (function (IPython) { TextCell.prototype.edit_mode = function () { var cont = IPython.Cell.prototype.edit_mode.apply(this); if (cont) { + this.unrender(); this.focus_editor(); }; return cont; @@ -234,6 +247,7 @@ var IPython = (function (IPython) { if (this.rendered) { return true; } else { + var cursor = this.code_mirror.getCursor(); if (cursor.line === 0 && cursor.ch === 0) { return true; } else { @@ -369,10 +383,12 @@ var IPython = (function (IPython) { * @extends IPython.TextCell */ var RawCell = function (options) { - options = this.mergeopt(RawCell, options); - - this.cell_type = 'raw'; + + options = this.mergeopt(RawCell,options) TextCell.apply(this, [options]); + this.cell_type = 'raw'; + // RawCell should always hide its rendered div + this.element.find('div.text_cell_render').hide(); }; RawCell.options_default = { From d1375354315a88faf36d0788e404bd22df23db46 Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Sat, 20 Jul 2013 09:38:20 -0700 Subject: [PATCH 05/56] Don't bind notebook keyboard events to $(document). Keyboard events were previously bound to $(document), which caused problems with our command mode not allowing other focused elements (dialogs, etc.) to manage their own keyboard events. We now bind to the notebook element. --- IPython/html/static/notebook/js/notebook.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 0905c1543..cbd2d92e7 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -155,7 +155,9 @@ var IPython = (function (IPython) { }); }); - $(document).keydown(function (event) { + // $(document).keydown(function (event) { + this.element.keydown(function (event) { + // console.log(event); // Event handlers for both command and edit mode if ((event.ctrlKey || event.metaKey) && event.keyCode==83) { From a37443710b76c55be0f8e2fa7be2969bd6f61741 Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Wed, 25 Sep 2013 17:03:59 -0700 Subject: [PATCH 06/56] Binding to notebook div not document. --- IPython/html/static/notebook/js/notebook.js | 33 ++++++++++++++----- .../html/static/notebook/less/notebook.less | 1 + IPython/html/static/style/style.min.css | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index cbd2d92e7..5bff8ac79 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -93,12 +93,14 @@ var IPython = (function (IPython) { * @method create_elements */ Notebook.prototype.create_elements = function () { + var that = this; + // We need the notebook div to be focusable so it can watch for keyboard events. + this.element.attr('tabindex','-1'); + this.container = $("
").addClass("container").attr("id", "notebook-container"); // We add this end_space div to the end of the notebook div to: // i) provide a margin between the last cell and the end of the notebook // ii) to prevent the div from scrolling up when the last cell is being // edited, but is too low on the page, which browsers will do automatically. - var that = this; - this.container = $("
").addClass("container").attr("id", "notebook-container"); var end_space = $('
').addClass('end_space'); end_space.dblclick(function (e) { var ncells = that.ncells(); @@ -106,7 +108,6 @@ var IPython = (function (IPython) { }); this.element.append(this.container); this.container.append(end_space); - $('div#notebook').addClass('border-box-sizing'); }; /** @@ -155,7 +156,14 @@ var IPython = (function (IPython) { }); }); - // $(document).keydown(function (event) { + $(document).keydown(function (event) { + if (event.which === key.ESC) { + // Intercept escape at highest level to avoid closing + // websocket connection with firefox + event.preventDefault(); + } + }); + this.element.keydown(function (event) { // console.log(event); @@ -215,11 +223,11 @@ var IPython = (function (IPython) { }; }; // Event handlers for command mode - } else if (that.mode === 'command' && !(event.ctrlKey || event.altKey)) { + } else if (that.mode === 'command' && !(event.ctrlKey || event.altKey || event.metaKey)) { if (event.which === key.ENTER && !(event.ctrlKey || event.altKey || event.shiftKey)) { // Enter edit mode = ENTER alone that.edit_mode(); - return false + return false; } else if (event.which === key.UPARROW && !event.shiftKey) { var index = that.get_selected_index(); if (index !== 0 && index !== null) { @@ -309,7 +317,6 @@ var IPython = (function (IPython) { } else if (event.which === 83) { // Save notebook = s that.save_checkpoint(); - that.control_key_active = false; return false; } else if (event.which === 74) { // Move cell down = j @@ -349,7 +356,6 @@ var IPython = (function (IPython) { return false; }; }; - // If we havn't handled it, let someone else. return true; }); @@ -763,6 +769,7 @@ var IPython = (function (IPython) { tomove.detach(); pivot.before(tomove); this.select(i-1); + this.element.focus(); }; this.set_dirty(true); @@ -787,6 +794,7 @@ var IPython = (function (IPython) { tomove.detach(); pivot.after(tomove); this.select(i+1); + this.element.focus(); }; }; this.set_dirty(); @@ -822,6 +830,7 @@ var IPython = (function (IPython) { }; $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i}); this.set_dirty(true); + this.element.focus(); }; return this; }; @@ -979,6 +988,8 @@ var IPython = (function (IPython) { // to this state, instead of a blank cell target_cell.code_mirror.clearHistory(); source_element.remove(); + this.select(i); + this.edit_mode(); this.set_dirty(true); }; }; @@ -1130,6 +1141,7 @@ var IPython = (function (IPython) { Notebook.prototype.cut_cell = function () { this.copy_cell(); this.delete_cell(); + this.element.focus(); } /** @@ -1156,6 +1168,7 @@ var IPython = (function (IPython) { var old_cell = this.get_next_cell(new_cell); this.delete_cell(this.find_cell_index(old_cell)); this.select(this.find_cell_index(new_cell)); + this.element.focus(); }; }; @@ -1169,6 +1182,7 @@ var IPython = (function (IPython) { var cell_data = this.clipboard; var new_cell = this.insert_cell_above(cell_data.cell_type); new_cell.fromJSON(cell_data); + this.element.focus(); }; }; @@ -1182,6 +1196,7 @@ var IPython = (function (IPython) { var cell_data = this.clipboard; var new_cell = this.insert_cell_below(cell_data.cell_type); new_cell.fromJSON(cell_data); + this.element.focus(); }; }; @@ -1215,6 +1230,7 @@ var IPython = (function (IPython) { this.select(current_index); this.undelete_backup = null; this.undelete_index = null; + this.element.focus(); } $('#undelete_cell').addClass('disabled'); } @@ -1987,6 +2003,7 @@ var IPython = (function (IPython) { } else { this.select(0); this.command_mode(); + this.element.focus(); }; this.set_dirty(false); this.scroll_to_top(); diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less index b51f3562b..e6f8bd59d 100644 --- a/IPython/html/static/notebook/less/notebook.less +++ b/IPython/html/static/notebook/less/notebook.less @@ -28,6 +28,7 @@ div#notebook { padding: 5px 5px 15px 5px; margin: 0px; border-top: 1px solid @border_color; + outline: none; } div.ui-widget-content { diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 88d3332db..873850768 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -1533,7 +1533,7 @@ body{background-color:#ffffff;} body.notebook_app{overflow:hidden;} span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%;} 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);} -div#notebook{overflow-y:scroll;overflow-x:auto;width:100%;padding:5px 5px 15px 5px;margin:0px;border-top:1px solid #ababab;} +div#notebook{overflow-y:scroll;overflow-x:auto;width:100%;padding:5px 5px 15px 5px;margin:0px;border-top:1px solid #ababab;outline:none;} div.ui-widget-content{border:1px solid #ababab;outline:none;} pre.dialog{background-color:#f7f7f7;border:1px solid #ddd;border-radius:4px;padding:0.4em;padding-left:2em;} p.dialog{padding:0.2em;} From c7758007144151e13b548c903db32cddc8bf603d Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Thu, 24 Oct 2013 13:31:29 -0700 Subject: [PATCH 07/56] Removing manual focusing of notebook div. --- IPython/html/static/notebook/js/notebook.js | 10 ---------- IPython/html/static/notebook/less/notebook.less | 1 + IPython/html/static/style/style.min.css | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 5bff8ac79..70aa2e95a 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -769,8 +769,6 @@ var IPython = (function (IPython) { tomove.detach(); pivot.before(tomove); this.select(i-1); - this.element.focus(); - }; this.set_dirty(true); }; @@ -794,7 +792,6 @@ var IPython = (function (IPython) { tomove.detach(); pivot.after(tomove); this.select(i+1); - this.element.focus(); }; }; this.set_dirty(); @@ -830,7 +827,6 @@ var IPython = (function (IPython) { }; $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i}); this.set_dirty(true); - this.element.focus(); }; return this; }; @@ -1141,7 +1137,6 @@ var IPython = (function (IPython) { Notebook.prototype.cut_cell = function () { this.copy_cell(); this.delete_cell(); - this.element.focus(); } /** @@ -1168,7 +1163,6 @@ var IPython = (function (IPython) { var old_cell = this.get_next_cell(new_cell); this.delete_cell(this.find_cell_index(old_cell)); this.select(this.find_cell_index(new_cell)); - this.element.focus(); }; }; @@ -1182,7 +1176,6 @@ var IPython = (function (IPython) { var cell_data = this.clipboard; var new_cell = this.insert_cell_above(cell_data.cell_type); new_cell.fromJSON(cell_data); - this.element.focus(); }; }; @@ -1196,7 +1189,6 @@ var IPython = (function (IPython) { var cell_data = this.clipboard; var new_cell = this.insert_cell_below(cell_data.cell_type); new_cell.fromJSON(cell_data); - this.element.focus(); }; }; @@ -1230,7 +1222,6 @@ var IPython = (function (IPython) { this.select(current_index); this.undelete_backup = null; this.undelete_index = null; - this.element.focus(); } $('#undelete_cell').addClass('disabled'); } @@ -2003,7 +1994,6 @@ var IPython = (function (IPython) { } else { this.select(0); this.command_mode(); - this.element.focus(); }; this.set_dirty(false); this.scroll_to_top(); diff --git a/IPython/html/static/notebook/less/notebook.less b/IPython/html/static/notebook/less/notebook.less index e6f8bd59d..7ce4736e1 100644 --- a/IPython/html/static/notebook/less/notebook.less +++ b/IPython/html/static/notebook/less/notebook.less @@ -29,6 +29,7 @@ div#notebook { margin: 0px; border-top: 1px solid @border_color; outline: none; + .border-box-sizing(); } div.ui-widget-content { diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 873850768..b1361c441 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -1533,7 +1533,7 @@ body{background-color:#ffffff;} body.notebook_app{overflow:hidden;} span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%;} 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);} -div#notebook{overflow-y:scroll;overflow-x:auto;width:100%;padding:5px 5px 15px 5px;margin:0px;border-top:1px solid #ababab;outline:none;} +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;} div.ui-widget-content{border:1px solid #ababab;outline:none;} pre.dialog{background-color:#f7f7f7;border:1px solid #ddd;border-radius:4px;padding:0.4em;padding-left:2em;} p.dialog{padding:0.2em;} From caffba142eda359a000c0790b5bb36e2d8f1579b Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Thu, 5 Dec 2013 13:22:12 -0800 Subject: [PATCH 08/56] Adding keyboard manager logic. This is currently very broken. --- IPython/html/static/base/js/dialog.js | 17 +- IPython/html/static/notebook/js/codecell.js | 2 +- IPython/html/static/notebook/js/completer.js | 32 +- .../static/notebook/js/keyboardmanager.js | 280 ++++++++++++++++++ IPython/html/static/notebook/js/main.js | 1 + IPython/html/static/notebook/js/notebook.js | 209 +------------ IPython/html/templates/notebook.html | 1 + 7 files changed, 316 insertions(+), 226 deletions(-) create mode 100644 IPython/html/static/notebook/js/keyboardmanager.js diff --git a/IPython/html/static/base/js/dialog.js b/IPython/html/static/base/js/dialog.js index 7fbcd7c30..4ec8ec088 100644 --- a/IPython/html/static/base/js/dialog.js +++ b/IPython/html/static/base/js/dialog.js @@ -65,13 +65,16 @@ IPython.dialog = (function (IPython) { dialog.remove(); }); } - if (options.reselect_cell !== false) { - dialog.on("hidden", function () { - if (IPython.notebook) { - var cell = IPython.notebook.get_selected_cell(); - if (cell) cell.select(); - } - }); + dialog.on("hidden", function () { + if (IPython.notebook) { + var cell = IPython.notebook.get_selected_cell(); + if (cell) cell.select(); + IPython.keyboard_manager.command_mode(); + } + }); + + if (IPython.keyboard_manager) { + IPython.keyboard_manager.null_mode(); } return dialog.modal(options); diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 164c72da2..335e1efa2 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -160,7 +160,7 @@ var IPython = (function (IPython) { var that = this; if (this.mode === 'command') { - return false + return true; } else if (this.mode === 'edit') { // whatever key is pressed, first, cancel the tooltip request before // they are sent, and remove tooltip if any, except for tab again diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index cfb2e90fd..c6dcba081 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -244,21 +244,28 @@ var IPython = (function (IPython) { //build the container var that = this; - this.sel.dblclick(function () { - that.pick(); - }); - this.sel.blur(this.close); - this.sel.keydown(function (event) { - that.keydown(event); - }); + // this.sel.dblclick(function () { + // that.pick(); + // }); + // this.sel.blur(this.close); + // this.sel.keydown(function (event) { + // that.keydown(event); + // }); this.build_gui_list(this.raw_result); - this.sel.focus(); - // Opera sometimes ignores focusing a freshly created node - if (window.opera) setTimeout(function () { - if (!this.done) this.sel.focus(); + setTimeout(function () { + console.log('doing it'); + that.sel.focus(); }, 100); + // this.sel.focus(); + // This needs to be after the focus() call because that puts the notebook into + // command mode. + // IPython.keyboard_manager.null_mode(); + // Opera sometimes ignores focusing a freshly created node + // if (window.opera) setTimeout(function () { + // if (!this.done) this.sel.focus(); + // }, 100); return true; } @@ -279,6 +286,8 @@ var IPython = (function (IPython) { if (this.done) return; this.done = true; $('.completions').remove(); + console.log('closing...') + IPython.keyboard_manager.edit_mode(); } Completer.prototype.pick = function () { @@ -292,6 +301,7 @@ var IPython = (function (IPython) { Completer.prototype.keydown = function (event) { + console.log('keydown', event.keyCode); var code = event.keyCode; var that = this; var special_key = false; diff --git a/IPython/html/static/notebook/js/keyboardmanager.js b/IPython/html/static/notebook/js/keyboardmanager.js new file mode 100644 index 000000000..4f5442662 --- /dev/null +++ b/IPython/html/static/notebook/js/keyboardmanager.js @@ -0,0 +1,280 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2011 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// Keyboard management +//============================================================================ + +var IPython = (function (IPython) { + "use strict"; + + var key = IPython.utils.keycodes; + + var KeyboardManager = function () { + this.mode = 'null'; + this.last_mode = 'null'; + this.bind_events(); + }; + + KeyboardManager.prototype.bind_events = function () { + var that = this; + $(document).keydown(function (event) { + return that.handle_keydown(event); + }); + }; + + KeyboardManager.prototype.handle_keydown = function (event) { + var notebook = IPython.notebook; + + console.log('keyboard_manager', this.mode, event); + + if (event.which === key.ESC) { + // Intercept escape at highest level to avoid closing + // websocket connection with firefox + event.preventDefault(); + } + + if (this.mode === 'null') { + return this.handle_edit_mode(event); + } + + // Event handlers for both command and edit mode + if ((event.ctrlKey || event.metaKey) && event.keyCode==83) { + // Save (CTRL+S) or (Command+S on Mac) + notebook.save_checkpoint(); + event.preventDefault(); + return false; + } else if (event.which === key.ESC) { + // Intercept escape at highest level to avoid closing + // websocket connection with firefox + event.preventDefault(); + // Don't return yet to allow edit/command modes to handle + } else if (event.which === key.SHIFT) { + // ignore shift keydown + return true; + } else if (event.which === key.ENTER && event.shiftKey) { + notebook.execute_selected_cell('shift'); + return false; + } else if (event.which === key.ENTER && event.altKey) { + // Execute code cell, and insert new in place + notebook.execute_selected_cell('alt'); + return false; + } else if (event.which === key.ENTER && event.ctrlKey) { + notebook.execute_selected_cell('ctrl'); + return false; + } + + if (this.mode === 'edit') { + return this.handle_edit_mode(event); + } else if (this.mode === 'command' && !(event.ctrlKey || event.altKey || event.metaKey)) { + return this.handle_command_mode(event); + } + } + + KeyboardManager.prototype.handle_null_mode = function (event) { + return true; + } + + + KeyboardManager.prototype.handle_edit_mode = function (event) { + var notebook = IPython.notebook; + + if (event.which === key.ESC) { + // ESC + notebook.command_mode(); + return false; + } else if (event.which === 77 && event.ctrlKey) { + // Ctrl-m + notebook.command_mode(); + return false; + } else if (event.which === key.UPARROW && !event.shiftKey) { + var cell = notebook.get_selected_cell(); + if (cell && cell.at_top()) { + event.preventDefault(); + notebook.command_mode() + notebook.select_prev(); + notebook.edit_mode(); + return false; + }; + } else if (event.which === key.DOWNARROW && !event.shiftKey) { + var cell = notebook.get_selected_cell(); + if (cell && cell.at_bottom()) { + event.preventDefault(); + notebook.command_mode() + notebook.select_next(); + notebook.edit_mode(); + return false; + }; + }; + return true; + } + + KeyboardManager.prototype.handle_command_mode = function (event) { + var notebook = IPython.notebook; + + if (event.which === key.ENTER && !(event.ctrlKey || event.altKey || event.shiftKey)) { + // Enter edit mode = ENTER alone + notebook.edit_mode(); + return false; + } else if (event.which === key.UPARROW && !event.shiftKey) { + var index = notebook.get_selected_index(); + if (index !== 0 && index !== null) { + notebook.select_prev(); + var cell = notebook.get_selected_cell(); + cell.focus_cell(); + }; + return false; + } else if (event.which === key.DOWNARROW && !event.shiftKey) { + var index = notebook.get_selected_index(); + if (index !== (notebook.ncells()-1) && index !== null) { + notebook.select_next(); + var cell = notebook.get_selected_cell(); + cell.focus_cell(); + }; + return false; + } else if (event.which === 88) { + // Cut selected cell = x + notebook.cut_cell(); + return false; + } else if (event.which === 67) { + // Copy selected cell = c + notebook.copy_cell(); + return false; + } else if (event.which === 86) { + // Paste below selected cell = v + notebook.paste_cell_below(); + return false; + } else if (event.which === 68) { + // Delete selected cell = d + notebook.delete_cell(); + return false; + } else if (event.which === 65) { + // Insert code cell above selected = a + notebook.insert_cell_above('code'); + notebook.select_prev(); + return false; + } else if (event.which === 66) { + // Insert code cell below selected = b + notebook.insert_cell_below('code'); + notebook.select_next(); + return false; + } else if (event.which === 89) { + // To code = y + notebook.to_code(); + return false; + } else if (event.which === 77) { + // To markdown = m + notebook.to_markdown(); + return false; + } else if (event.which === 84) { + // To Raw = t + notebook.to_raw(); + return false; + } else if (event.which === 49) { + // To Heading 1 = 1 + notebook.to_heading(undefined, 1); + return false; + } else if (event.which === 50) { + // To Heading 2 = 2 + notebook.to_heading(undefined, 2); + return false; + } else if (event.which === 51) { + // To Heading 3 = 3 + notebook.to_heading(undefined, 3); + return false; + } else if (event.which === 52) { + // To Heading 4 = 4 + notebook.to_heading(undefined, 4); + return false; + } else if (event.which === 53) { + // To Heading 5 = 5 + notebook.to_heading(undefined, 5); + return false; + } else if (event.which === 54) { + // To Heading 6 = 6 + notebook.to_heading(undefined, 6); + return false; + } else if (event.which === 79) { + // Toggle output = o + if (event.shiftKey) { + notebook.toggle_output_scroll(); + } else { + notebook.toggle_output(); + }; + return false; + } else if (event.which === 83) { + // Save notebook = s + notebook.save_checkpoint(); + return false; + } else if (event.which === 74) { + // Move cell down = j + notebook.move_cell_down(); + return false; + } else if (event.which === 75) { + // Move cell up = k + notebook.move_cell_up(); + return false; + } else if (event.which === 80) { + // Select previous = p + notebook.select_prev(); + return false; + } else if (event.which === 78) { + // Select next = n + notebook.select_next(); + return false; + } else if (event.which === 76) { + // Toggle line numbers = l + notebook.cell_toggle_line_numbers(); + return false; + } else if (event.which === 73) { + // Interrupt kernel = i + notebook.kernel.interrupt(); + return false; + } else if (event.which === 190) { + // Restart kernel = . # matches qt console + notebook.restart_kernel(); + return false; + } else if (event.which === 72) { + // Show keyboard shortcuts = h + IPython.quick_help.show_keyboard_shortcuts(); + return false; + } else if (event.which === 90) { + // Undo last cell delete = z + notebook.undelete(); + return false; + }; + // If we havn't handled it, let someone else. + return true; + }; + + KeyboardManager.prototype.edit_mode = function () { + this.last_mode = this.mode; + this.mode = 'edit'; + } + + KeyboardManager.prototype.command_mode = function () { + this.last_mode = this.mode; + this.mode = 'command'; + } + + KeyboardManager.prototype.null_mode = function () { + this.last_mode = this.mode; + this.mode = 'null'; + } + + KeyboardManager.prototype.last_mode = function () { + var lm = this.last_mode; + this.last_mode = this.mode; + this.mode = lm; + } + + + IPython.KeyboardManager = KeyboardManager; + + return IPython; + +}(IPython)); diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index e0ac198f6..0caeaec74 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -62,6 +62,7 @@ function (marked) { IPython.quick_help = new IPython.QuickHelp(); IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl}); IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName}); + IPython.keyboard_manager = new IPython.KeyboardManager(); IPython.save_widget = new IPython.SaveWidget('span#save_widget'); IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath}) IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container') diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 70aa2e95a..8b4aa3f35 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -13,7 +13,6 @@ var IPython = (function (IPython) { "use strict"; var utils = IPython.utils; - var key = IPython.utils.keycodes; /** * A notebook contains and manages cells. @@ -51,7 +50,6 @@ var IPython = (function (IPython) { this.minimum_autosave_interval = 120000; // single worksheet for now this.worksheet_metadata = {}; - this.control_key_active = false; this.notebook_name_blacklist_re = /[\/\\:]/; this.nbformat = 3 // Increment this when changing the nbformat this.nbformat_minor = 0 // Increment this when changing the nbformat @@ -94,7 +92,6 @@ var IPython = (function (IPython) { */ Notebook.prototype.create_elements = function () { var that = this; - // We need the notebook div to be focusable so it can watch for keyboard events. this.element.attr('tabindex','-1'); this.container = $("
").addClass("container").attr("id", "notebook-container"); // We add this end_space div to the end of the notebook div to: @@ -156,210 +153,6 @@ var IPython = (function (IPython) { }); }); - $(document).keydown(function (event) { - if (event.which === key.ESC) { - // Intercept escape at highest level to avoid closing - // websocket connection with firefox - event.preventDefault(); - } - }); - - this.element.keydown(function (event) { - // console.log(event); - - // Event handlers for both command and edit mode - if ((event.ctrlKey || event.metaKey) && event.keyCode==83) { - // Save (CTRL+S) or (Command+S on Mac) - that.save_checkpoint(); - event.preventDefault(); - return false; - } else if (event.which === key.ESC) { - // Intercept escape at highest level to avoid closing - // websocket connection with firefox - event.preventDefault(); - // Don't return yet to allow edit/command modes to handle - } else if (event.which === key.SHIFT) { - // ignore shift keydown - return true; - } else if (event.which === key.ENTER && event.shiftKey) { - that.execute_selected_cell('shift'); - return false; - } else if (event.which === key.ENTER && event.altKey) { - // Execute code cell, and insert new in place - that.execute_selected_cell('alt'); - return false; - } else if (event.which === key.ENTER && event.ctrlKey) { - that.execute_selected_cell('ctrl'); - return false; - } - - // Event handlers for edit mode - if (that.mode === 'edit') { - if (event.which === key.ESC) { - // ESC - that.command_mode(); - return false; - } else if (event.which === 77 && event.ctrlKey) { - // Ctrl-m - that.command_mode(); - return false; - } else if (event.which === key.UPARROW && !event.shiftKey) { - var cell = that.get_selected_cell(); - if (cell && cell.at_top()) { - event.preventDefault(); - that.command_mode() - that.select_prev(); - that.edit_mode(); - return false; - }; - } else if (event.which === key.DOWNARROW && !event.shiftKey) { - var cell = that.get_selected_cell(); - if (cell && cell.at_bottom()) { - event.preventDefault(); - that.command_mode() - that.select_next(); - that.edit_mode(); - return false; - }; - }; - // Event handlers for command mode - } else if (that.mode === 'command' && !(event.ctrlKey || event.altKey || event.metaKey)) { - if (event.which === key.ENTER && !(event.ctrlKey || event.altKey || event.shiftKey)) { - // Enter edit mode = ENTER alone - that.edit_mode(); - return false; - } else if (event.which === key.UPARROW && !event.shiftKey) { - var index = that.get_selected_index(); - if (index !== 0 && index !== null) { - that.select_prev(); - var cell = that.get_selected_cell(); - cell.focus_cell(); - }; - return false; - } else if (event.which === key.DOWNARROW && !event.shiftKey) { - var index = that.get_selected_index(); - if (index !== (that.ncells()-1) && index !== null) { - that.select_next(); - var cell = that.get_selected_cell(); - cell.focus_cell(); - }; - return false; - } else if (event.which === 88) { - // Cut selected cell = x - that.cut_cell(); - return false; - } else if (event.which === 67) { - // Copy selected cell = c - that.copy_cell(); - return false; - } else if (event.which === 86) { - // Paste below selected cell = v - that.paste_cell_below(); - return false; - } else if (event.which === 68) { - // Delete selected cell = d - that.delete_cell(); - return false; - } else if (event.which === 65) { - // Insert code cell above selected = a - that.insert_cell_above('code'); - that.select_prev(); - return false; - } else if (event.which === 66) { - // Insert code cell below selected = b - that.insert_cell_below('code'); - that.select_next(); - return false; - } else if (event.which === 89) { - // To code = y - that.to_code(); - return false; - } else if (event.which === 77) { - // To markdown = m - that.to_markdown(); - return false; - } else if (event.which === 84) { - // To Raw = t - that.to_raw(); - return false; - } else if (event.which === 49) { - // To Heading 1 = 1 - that.to_heading(undefined, 1); - return false; - } else if (event.which === 50) { - // To Heading 2 = 2 - that.to_heading(undefined, 2); - return false; - } else if (event.which === 51) { - // To Heading 3 = 3 - that.to_heading(undefined, 3); - return false; - } else if (event.which === 52) { - // To Heading 4 = 4 - that.to_heading(undefined, 4); - return false; - } else if (event.which === 53) { - // To Heading 5 = 5 - that.to_heading(undefined, 5); - return false; - } else if (event.which === 54) { - // To Heading 6 = 6 - that.to_heading(undefined, 6); - return false; - } else if (event.which === 79) { - // Toggle output = o - if (event.shiftKey) { - that.toggle_output_scroll(); - } else { - that.toggle_output(); - }; - return false; - } else if (event.which === 83) { - // Save notebook = s - that.save_checkpoint(); - return false; - } else if (event.which === 74) { - // Move cell down = j - that.move_cell_down(); - return false; - } else if (event.which === 75) { - // Move cell up = k - that.move_cell_up(); - return false; - } else if (event.which === 80) { - // Select previous = p - that.select_prev(); - return false; - } else if (event.which === 78) { - // Select next = n - that.select_next(); - return false; - } else if (event.which === 76) { - // Toggle line numbers = l - that.cell_toggle_line_numbers(); - return false; - } else if (event.which === 73) { - // Interrupt kernel = i - that.kernel.interrupt(); - return false; - } else if (event.which === 190) { - // Restart kernel = . # matches qt console - that.restart_kernel(); - return false; - } else if (event.which === 72) { - // Show keyboard shortcuts = h - IPython.quick_help.show_keyboard_shortcuts(); - return false; - } else if (event.which === 90) { - // Undo last cell delete = z - that.undelete(); - return false; - }; - }; - // If we havn't handled it, let someone else. - return true; - }); - var collapse_time = function (time) { var app_height = $('#ipython-main-app').height(); // content height var splitter_height = $('div#pager_splitter').outerHeight(true); @@ -735,6 +528,7 @@ var IPython = (function (IPython) { this.mode = 'command'; }; }; + IPython.keyboard_manager.command_mode(); }; Notebook.prototype.edit_mode = function () { @@ -747,6 +541,7 @@ var IPython = (function (IPython) { cell.edit_mode(); this.mode = 'edit'; }; + IPython.keyboard_manager.edit_mode(); }; }; diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index 84e8c37db..8270856af 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -291,6 +291,7 @@ class="notebook_app" + From c1166ea98a393c9755f5d07c385eac2824599741 Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Fri, 6 Dec 2013 13:39:33 -0800 Subject: [PATCH 09/56] Lots of updates and changes. * Cleaning up the code mirror event handlers. * Adding logic to prevent calling Notebook.command_mode on cell focusout. * Fixing bugs. --- IPython/html/static/notebook/js/cell.js | 31 ++++- IPython/html/static/notebook/js/codecell.js | 119 +++++++++--------- IPython/html/static/notebook/js/completer.js | 30 ++--- .../static/notebook/js/keyboardmanager.js | 11 +- IPython/html/static/notebook/js/notebook.js | 22 ++-- IPython/html/static/notebook/js/textcell.js | 60 +++++---- 6 files changed, 157 insertions(+), 116 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index fb1e971c0..cc365a348 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -120,8 +120,37 @@ var IPython = (function (IPython) { }; }); that.element.focusout(function (event) { + var is_or_has = function (a, b) { + // Is b a child of a or a itself? + return a.has(b).length !==0 || a.is(b); + } if (that.mode === 'edit') { - $([IPython.events]).trigger('command_mode.Cell', {'cell':that}); + setTimeout(function () { + var trigger = true; + var target = $(document.activeElement); + var completer = that.element.find($('div.completions')); + var tooltip = $('div#tooltip') + if (target.length > 0) { + // If the focused element (target) is inside the cell + // (that.element) don't enter command mode. + if (is_or_has(that.element, target)) { + trigger = false; + // The focused element is outside the cell + } else { + // If the focused element is the tooltip or completer + // don't enter command mode, otherwise do. + trigger = true; + if (tooltip.length > 0 && is_or_has(tooltip, target)) { + trigger = false; + } else if (completer.length > 0 && is_or_has(completer, target)) { + trigger = false; + } + } + } + if (trigger) { + $([IPython.events]).trigger('command_mode.Cell', {'cell':that}); + } + }, 1); }; }); if (this.code_mirror) { diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 335e1efa2..b7957bbf1 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -74,7 +74,7 @@ var IPython = (function (IPython) { var cm_overwrite_options = { - onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this) + onKeyEvent: $.proxy(this.handle_keyevent,this) }; options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options}); @@ -149,6 +149,17 @@ var IPython = (function (IPython) { ); }; + CodeCell.prototype.handle_keyevent = function (editor, event) { + + console.log('CM', this.mode, event.which, event.type) + + if (this.mode === 'command') { + return true; + } else if (this.mode === 'edit') { + return this.handle_codemirror_keyevent(editor, event); + } + }; + /** * This method gets called in CodeMirror's onKeyDown/onKeyPress * handlers and is used to provide custom key handling. Its return @@ -157,61 +168,55 @@ var IPython = (function (IPython) { * @method handle_codemirror_keyevent */ CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) { + var that = this; + // whatever key is pressed, first, cancel the tooltip request before + // they are sent, and remove tooltip if any, except for tab again + if (event.type === 'keydown' && event.which != key.TAB ) { + IPython.tooltip.remove_and_cancel_tooltip(); + } - if (this.mode === 'command') { - return true; - } else if (this.mode === 'edit') { - // whatever key is pressed, first, cancel the tooltip request before - // they are sent, and remove tooltip if any, except for tab again - if (event.type === 'keydown' && event.which != key.TAB ) { - IPython.tooltip.remove_and_cancel_tooltip(); - }; - - var cur = editor.getCursor(); - if (event.keyCode === key.ENTER){ - this.auto_highlight(); - } + var cur = editor.getCursor(); + if (event.keyCode === key.ENTER){ + this.auto_highlight(); + } - if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) { - // Always ignore shift-enter in CodeMirror as we handle it. + if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) { + // Always ignore shift-enter in CodeMirror as we handle it. + return true; + } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) { + // triger on keypress (!) otherwise inconsistent event.which depending on plateform + // browser and keyboard layout ! + // Pressing '(' , request tooltip, don't forget to reappend it + // The second argument says to hide the tooltip if the docstring + // is actually empty + IPython.tooltip.pending(that, true); + } else if (event.which === key.UPARROW && event.type === 'keydown') { + // If we are not at the top, let CM handle the up arrow and + // prevent the global keydown handler from handling it. + if (!that.at_top()) { + event.stop(); + return false; + } else { return true; - - } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) { - // triger on keypress (!) otherwise inconsistent event.which depending on plateform - // browser and keyboard layout ! - // Pressing '(' , request tooltip, don't forget to reappend it - // The second argument says to hide the tooltip if the docstring - // is actually empty - IPython.tooltip.pending(that, true); - } else if (event.which === key.UPARROW && event.type === 'keydown') { - // If we are not at the top, let CM handle the up arrow and - // prevent the global keydown handler from handling it. - if (!that.at_top()) { - event.stop(); - return false; - } else { - return true; - }; - } else if (event.which === key.ESC) { - IPython.tooltip.remove_and_cancel_tooltip(true); + } + } else if (event.which === key.ESC) { + return IPython.tooltip.remove_and_cancel_tooltip(true); + } else if (event.which === key.DOWNARROW && event.type === 'keydown') { + // If we are not at the bottom, let CM handle the down arrow and + // prevent the global keydown handler from handling it. + if (!that.at_bottom()) { + event.stop(); + return false; + } else { return true; - } else if (event.which === key.DOWNARROW && event.type === 'keydown') { - // If we are not at the bottom, let CM handle the down arrow and - // prevent the global keydown handler from handling it. - if (!that.at_bottom()) { - event.stop(); - return false; - } else { - return true; - }; - } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { - if (editor.somethingSelected()){ - var anchor = editor.getCursor("anchor"); - var head = editor.getCursor("head"); - if( anchor.line != head.line){ - return false; - } + } + } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { + if (editor.somethingSelected()){ + var anchor = editor.getCursor("anchor"); + var head = editor.getCursor("head"); + if( anchor.line != head.line){ + return false; } } IPython.tooltip.request(that); @@ -229,16 +234,18 @@ var IPython = (function (IPython) { // is empty. In this case, let CodeMirror handle indentation. return false; } else { - // keypress/keyup also trigger on TAB press, and we don't want to - // use those to disable tab completion. - return false; - }; + event.stop(); + this.completer.startCompletion(); + return true; + } + } else { + // keypress/keyup also trigger on TAB press, and we don't want to + // use those to disable tab completion. return false; } return false; }; - // Kernel related calls. CodeCell.prototype.set_kernel = function (kernel) { diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index c6dcba081..c2ea6028f 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -244,28 +244,24 @@ var IPython = (function (IPython) { //build the container var that = this; - // this.sel.dblclick(function () { - // that.pick(); - // }); - // this.sel.blur(this.close); - // this.sel.keydown(function (event) { - // that.keydown(event); - // }); + this.sel.dblclick(function () { + that.pick(); + }); + this.sel.blur(this.close); + this.sel.keydown(function (event) { + that.keydown(event); + }); this.build_gui_list(this.raw_result); - setTimeout(function () { - console.log('doing it'); - that.sel.focus(); - }, 100); - // this.sel.focus(); + this.sel.focus(); // This needs to be after the focus() call because that puts the notebook into // command mode. - // IPython.keyboard_manager.null_mode(); + IPython.keyboard_manager.null_mode(); // Opera sometimes ignores focusing a freshly created node - // if (window.opera) setTimeout(function () { - // if (!this.done) this.sel.focus(); - // }, 100); + if (window.opera) setTimeout(function () { + if (!this.done) this.sel.focus(); + }, 100); return true; } @@ -286,7 +282,6 @@ var IPython = (function (IPython) { if (this.done) return; this.done = true; $('.completions').remove(); - console.log('closing...') IPython.keyboard_manager.edit_mode(); } @@ -301,7 +296,6 @@ var IPython = (function (IPython) { Completer.prototype.keydown = function (event) { - console.log('keydown', event.keyCode); var code = event.keyCode; var that = this; var special_key = false; diff --git a/IPython/html/static/notebook/js/keyboardmanager.js b/IPython/html/static/notebook/js/keyboardmanager.js index 4f5442662..edf26de55 100644 --- a/IPython/html/static/notebook/js/keyboardmanager.js +++ b/IPython/html/static/notebook/js/keyboardmanager.js @@ -15,8 +15,8 @@ var IPython = (function (IPython) { var key = IPython.utils.keycodes; var KeyboardManager = function () { - this.mode = 'null'; - this.last_mode = 'null'; + this.mode = 'command'; + this.last_mode = 'command'; this.bind_events(); }; @@ -30,7 +30,7 @@ var IPython = (function (IPython) { KeyboardManager.prototype.handle_keydown = function (event) { var notebook = IPython.notebook; - console.log('keyboard_manager', this.mode, event); + console.log('keyboard_manager', this.mode, event.keyCode); if (event.which === key.ESC) { // Intercept escape at highest level to avoid closing @@ -39,7 +39,7 @@ var IPython = (function (IPython) { } if (this.mode === 'null') { - return this.handle_edit_mode(event); + return this.handle_null_mode(event); } // Event handlers for both command and edit mode @@ -252,16 +252,19 @@ var IPython = (function (IPython) { }; KeyboardManager.prototype.edit_mode = function () { + console.log('KeyboardManager', 'changing to edit mode'); this.last_mode = this.mode; this.mode = 'edit'; } KeyboardManager.prototype.command_mode = function () { + console.log('KeyboardManager', 'changing to command mode'); this.last_mode = this.mode; this.mode = 'command'; } KeyboardManager.prototype.null_mode = function () { + console.log('KeyboardManager', 'changing to null mode'); this.last_mode = this.mode; this.mode = 'null'; } diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index 8b4aa3f35..e57ff03f0 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -38,6 +38,8 @@ var IPython = (function (IPython) { this.undelete_index = null; this.undelete_below = false; this.paste_enabled = false; + // It is important to start out in command mode to match the intial mode + // of the KeyboardManager. this.mode = 'command'; this.set_dirty(false); this.metadata = {}; @@ -521,27 +523,27 @@ var IPython = (function (IPython) { Notebook.prototype.command_mode = function () { if (this.mode !== 'command') { + console.log('\nNotebook', 'changing to command mode'); var index = this.get_edit_index(); var cell = this.get_cell(index); if (cell) { cell.command_mode(); - this.mode = 'command'; }; + this.mode = 'command'; + IPython.keyboard_manager.command_mode(); }; - IPython.keyboard_manager.command_mode(); }; Notebook.prototype.edit_mode = function () { if (this.mode !== 'edit') { - // We are in command mode so get_edit_index() is null!!! - var index = this.get_selected_index(); - if (index === null) {return;} // No cell is selected - var cell = this.get_cell(index); - if (cell) { - cell.edit_mode(); - this.mode = 'edit'; - }; + console.log('\nNotebook', 'changing to edit mode'); + var cell = this.get_selected_cell(); + if (cell === null) {return;} // No cell is selected + // We need to set the mode to edit to prevent reentering this method + // when cell.edit_mode() is called below. + this.mode = 'edit'; IPython.keyboard_manager.edit_mode(); + cell.edit_mode(); }; }; diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js index 5bc05f2a2..45ddf1933 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -41,7 +41,7 @@ var IPython = (function (IPython) { // we cannot put this as a class key as it has handle to "this". var cm_overwrite_options = { - onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this) + onKeyEvent: $.proxy(this.handle_keyevent,this) }; options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options}); @@ -109,6 +109,16 @@ var IPython = (function (IPython) { }); }; + TextCell.prototype.handle_keyevent = function (editor, event) { + + console.log('CM', this.mode, event.which, event.type) + + if (this.mode === 'command') { + return true; + } else if (this.mode === 'edit') { + return this.handle_codemirror_keyevent(editor, event); + } + }; /** * This method gets called in CodeMirror's onKeyDown/onKeyPress @@ -123,33 +133,29 @@ var IPython = (function (IPython) { */ TextCell.prototype.handle_codemirror_keyevent = function (editor, event) { var that = this; - if (this.mode === 'command') { - return false - } else if (this.mode === 'edit') { - if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) { - // Always ignore shift-enter in CodeMirror as we handle it. + + if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) { + // Always ignore shift-enter in CodeMirror as we handle it. + return true; + } else if (event.which === key.UPARROW && event.type === 'keydown') { + // If we are not at the top, let CM handle the up arrow and + // prevent the global keydown handler from handling it. + if (!that.at_top()) { + event.stop(); + return false; + } else { return true; - } else if (event.which === key.UPARROW && event.type === 'keydown') { - // If we are not at the top, let CM handle the up arrow and - // prevent the global keydown handler from handling it. - if (!that.at_top()) { - event.stop(); - return false; - } else { - return true; - }; - } else if (event.which === key.DOWNARROW && event.type === 'keydown') { - // If we are not at the bottom, let CM handle the down arrow and - // prevent the global keydown handler from handling it. - if (!that.at_bottom()) { - event.stop(); - return false; - } else { - return true; - }; - } - return false; - }; + }; + } else if (event.which === key.DOWNARROW && event.type === 'keydown') { + // If we are not at the bottom, let CM handle the down arrow and + // prevent the global keydown handler from handling it. + if (!that.at_bottom()) { + event.stop(); + return false; + } else { + return true; + }; + } return false; }; From 0000e688d685cc001a52901b125c82e35afbbb6c Mon Sep 17 00:00:00 2001 From: "Brian E. Granger" Date: Fri, 6 Dec 2013 15:06:11 -0800 Subject: [PATCH 10/56] Code comments and minor fixes. --- IPython/html/static/notebook/js/cell.js | 7 +++++-- IPython/html/static/notebook/js/completer.js | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index cc365a348..5813064f7 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -125,11 +125,14 @@ var IPython = (function (IPython) { return a.has(b).length !==0 || a.is(b); } if (that.mode === 'edit') { + // Most of the time, when a cell is in edit mode and focusout + // fires, it means we should enter command mode. But there are cases + // when we should not enter command mode. setTimeout(function () { var trigger = true; var target = $(document.activeElement); - var completer = that.element.find($('div.completions')); - var tooltip = $('div#tooltip') + var completer = $('div.completions'); + var tooltip = $('div#tooltip'); if (target.length > 0) { // If the focused element (target) is inside the cell // (that.element) don't enter command mode. diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index c2ea6028f..f089879f5 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -218,6 +218,8 @@ var IPython = (function (IPython) { this.complete = $('
').addClass('completions'); this.complete.attr('id', 'complete'); + // Currently webkit doesn't use the size attr correctly. See: + // https://code.google.com/p/chromium/issues/detail?id=4579 this.sel = $('