Search code examples
jupyter-notebookcodemirrorjupyter

"Down arrow" moves cursor to end of line - how to turn it off


In IPython Notebook / Jupyter, arrow up/down keystrokes within a cell are handled by CodeMirror (as far as I can tell). I use these actions a lot (re-bound to control-p / control-n) to move between cells; but at the end of every cell, the cursor moves to end of line first before jumping to the next cell. This is counter-intuitive and, to me, rather distracting.

Is there any way to configure CodeMirror to make this move down to be just that - a move down?

Thanks!


Solution

  • Knowing that I wasn't alone in wanting to skip the "going to end of line" behavior when going down from the last line of a code cell, I investigated that behavior and found out that:

    • it's CodeMirror that goes to the end of line when you type down in the last line of a code cell (file: codemirror.js ; "methods": findPosV and moveV)
    • and it's IPython that decides what to do with the "down" event after it has been handled by CodeMirror (file: cell.js ; class: Cell ; method: handle_codemirror_keyevent) ; looking at the code, I saw that IPython ignores the event when not at the last character of the last line.

    This essentially confirms Marijin's answer.

    The primary goal being to jump to the next cell, I think there's no need to prevent CodeMirror from going to the end of that line. The point is to force IPython to handle the event anyway.

    My solution was to change the code from Cell.prototype.handle_codemirror_keyevent to this:

    Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
        var shortcuts = this.keyboard_manager.edit_shortcuts;
    
        var cur = editor.getCursor();
        if((cur.line !== 0) && event.keyCode === 38){
            // going up, but not from the first line
            // don't do anything more with the event
            event._ipkmIgnore = true;
        }
        var nLastLine = editor.lastLine();
        if ((event.keyCode === 40) &&
             ((cur.line !== nLastLine))
           ) {
            // going down, but not from the last line
            // don't do anything more with the event
            event._ipkmIgnore = true;
        }
        // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
        // manager will handle it
        if (shortcuts.handles(event)) {
            return true;
        }
    
        return false;
    };
    

    This code provides the desired behavior for the "down-arrow" key (almost: the cursor still goes to the end of the line, except that we don't see it, as we're already in another cell at that point), and also handles the "up-arrow" key similarly.

    To modify the handle_codemirror_keyevent prototype, you have two possibilities:

    1. You edit the cell.js file and change the code of the prototype to the code I gave above. The file is in <python>/Lib/site-packages/IPython/html/static/notebook/js or something similar depending on you distro
    2. Much better, after the page is loaded, you change that prototype dynamically by doing this:

      IPython.Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
          <same code as above>
      };
      

      You can do that in your custom.js for example, or create an extension to do it (that's what I did).