diff --git a/IPython/html/static/base/js/dialog.js b/IPython/html/static/base/js/dialog.js index 7fbcd7c..350bd2a 100644 --- a/IPython/html/static/base/js/dialog.js +++ b/IPython/html/static/base/js/dialog.js @@ -65,13 +65,17 @@ 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.enable(); + IPython.keyboard_manager.command_mode(); + } + }); + + if (IPython.keyboard_manager) { + IPython.keyboard_manager.disable(); } return dialog.modal(options); diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index aa4dcbe..b3f7b3a 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -455,6 +455,26 @@ IPython.utils = (function (IPython) { return M; })(); + 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); + } + + var is_focused = function (e) { + // Is element e, or one of its children focused? + e = $(e); + var target = $(document.activeElement); + if (target.length > 0) { + if (is_or_has(e, target)) { + return true; + } else { + return false; + } + } else { + return false; + } + } + return { regex_split : regex_split, @@ -475,7 +495,9 @@ IPython.utils = (function (IPython) { encode_uri_components : encode_uri_components, splitext : splitext, always_new : always_new, - browser : browser + browser : browser, + is_or_has : is_or_has, + is_focused : is_focused }; }(IPython)); diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index 502df10..636f61e 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -39,6 +39,8 @@ var IPython = (function (IPython) { this.placeholder = options.placeholder || ''; this.read_only = options.cm_config.readOnly; this.selected = false; + this.rendered = false; + this.mode = 'command'; this.metadata = {}; // load this from metadata later ? this.user_highlight = 'auto'; @@ -60,6 +62,7 @@ var IPython = (function (IPython) { if (this.element !== null) { this.element.data("cell", this); this.bind_events(); + this.init_classes(); } }; @@ -97,6 +100,26 @@ var IPython = (function (IPython) { Cell.prototype.create_element = function () { }; + Cell.prototype.init_classes = function () { + // Call after this.element exists to initialize the css classes + // related to selected, rendered and mode. + if (this.selected) { + this.element.addClass('selected'); + } else { + this.element.addClass('unselected'); + } + if (this.rendered) { + this.element.addClass('rendered'); + } else { + this.element.addClass('unrendered'); + } + if (this.mode === 'edit') { + this.element.addClass('edit_mode'); + } else { + this.element.addClass('command_mode'); + } + } + /** * Subclasses can implement override bind_events. @@ -108,20 +131,41 @@ var IPython = (function (IPython) { var that = this; // We trigger events so that Cell doesn't have to depend on Notebook. that.element.click(function (event) { - if (that.selected === false) { + if (!that.selected) { $([IPython.events]).trigger('select.Cell', {'cell':that}); - } + }; }); that.element.focusin(function (event) { - if (that.selected === false) { + if (!that.selected) { $([IPython.events]).trigger('select.Cell', {'cell':that}); - } + }; }); if (this.code_mirror) { 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}); + }); + } + if (this.code_mirror) { + this.code_mirror.on('blur', function(cm, change) { + if (that.mode === 'edit') { + setTimeout(function () { + var isf = IPython.utils.is_focused; + var trigger = true; + if (isf('div#tooltip') || isf('div.completions')) { + trigger = false; + } + if (trigger) { + $([IPython.events]).trigger('command_mode.Cell', {cell: that}); + } + }, 1); + } + }); + } }; /** @@ -129,47 +173,126 @@ 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]); } }; /** - * should be triggerd when cell is selected + * 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; + } }; - /** - * should be triggerd when cell is unselected + * 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; + } }; /** - * should be overritten by subclass - * @method get_text + * handle cell level logic when a cell is rendered + * @method render + * @return is the action being taken */ - Cell.prototype.get_text = function () { + Cell.prototype.render = function () { + if (!this.rendered) { + this.element.addClass('rendered'); + this.element.removeClass('unrendered'); + this.rendered = true; + return true; + } else { + return false; + } }; /** - * should be overritten by subclass - * @method set_text - * @param {string} text + * handle cell level logic when a cell is unrendered + * @method unrender + * @return is the action being taken */ - Cell.prototype.set_text = function (text) { + 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.refresh(); + this.code_mirror.focus(); + } + + /** * Refresh codemirror instance * @method refresh */ @@ -177,20 +300,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 132b3ab..c21a136 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}); @@ -139,6 +139,27 @@ 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(); } + ); + }; + + 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 @@ -151,8 +172,9 @@ var IPython = (function (IPython) { 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 + var tooltip_closed = null; if (event.type === 'keydown' && event.which != key.TAB ) { - IPython.tooltip.remove_and_cancel_tooltip(); + tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip(); } var cur = editor.getCursor(); @@ -160,7 +182,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; } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) { @@ -179,8 +201,32 @@ var IPython = (function (IPython) { } else { return true; } - } else if (event.which === key.ESC) { - return IPython.tooltip.remove_and_cancel_tooltip(true); + } else if (event.which === key.ESC && event.type === 'keydown') { + // First see if the tooltip is active and if so cancel it. + if (tooltip_closed) { + // The call to remove_and_cancel_tooltip above in L177 doesn't pass + // force=true. Because of this it won't actually close the tooltip + // if it is in sticky mode. Thus, we have to check again if it is open + // and close it with force=true. + if (!IPython.tooltip._hidden) { + IPython.tooltip.remove_and_cancel_tooltip(true); + } + // If we closed the tooltip, don't let CM or the global handlers + // handle this event. + event.stop(); + return true; + } + if (that.code_mirror.options.keyMap === "vim-insert") { + // vim keyMap is active and in insert mode. In this case we leave vim + // insert mode, but remain in notebook edit mode. + // Let' CM handle this event and prevent global handling. + event.stop(); + return false; + } else { + // vim keyMap is not active. Leave notebook edit mode. + // Don't let CM handle the event, defer to global handling. + 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. @@ -190,7 +236,7 @@ var IPython = (function (IPython) { } else { return true; } - } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { + } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) { if (editor.somethingSelected()){ var anchor = editor.getCursor("anchor"); var head = editor.getCursor("head"); @@ -225,7 +271,6 @@ var IPython = (function (IPython) { return false; }; - // Kernel related calls. CodeCell.prototype.set_kernel = function (kernel) { @@ -304,15 +349,32 @@ var IPython = (function (IPython) { // Basic cell manipulation. 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. + var cont = IPython.Cell.prototype.select.apply(this); + if (cont) { + this.code_mirror.refresh(); + this.auto_highlight(); + } + return cont; + }; + + CodeCell.prototype.render = function () { + var cont = IPython.Cell.prototype.render.apply(this); + // Always execute, even if we are already in the rendered state + return cont; + }; + + CodeCell.prototype.unrender = function () { + // CodeCell is always rendered + return false; }; + CodeCell.prototype.edit_mode = function () { + var cont = IPython.Cell.prototype.edit_mode.apply(this); + if (cont) { + this.focus_editor(); + } + return cont; + } CodeCell.prototype.select_all = function () { var start = {line: 0, ch: 0}; diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index cfb2e90..e3e4617 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 = $('