Skip to content
This repository has been archived by the owner on Aug 22, 2023. It is now read-only.

Customization

Jimmy Liu edited this page May 31, 2018 · 18 revisions

This wiki is a place to share snippets that enhance the usability of the vim - IPython integration, but aren't so general that they are part of the binding.

Disable automatic insertion of closing brackets and quotes

Jupyter automatically pairs the delimiters ( [ { ' " when typed in code cells. Add the following to ~/.jupyter/custom/custom.js to disable this feature:

IPython.CodeCell.options_default.cm_config.autoCloseBrackets = false;

See https://codemirror.net/doc/manual.html#addon_closebrackets for a complete description of this option.

Swap j/k and gj/gk

require([
  'nbextensions/vim_binding/vim_binding',   // depends your installation
], function() {
  // Swap j/k and gj/gk (Note that <Plug> mappings)
  CodeMirror.Vim.map("j", "<Plug>(vim-binding-gj)", "normal");
  CodeMirror.Vim.map("k", "<Plug>(vim-binding-gk)", "normal");
  CodeMirror.Vim.map("gj", "<Plug>(vim-binding-j)", "normal");
  CodeMirror.Vim.map("gk", "<Plug>(vim-binding-k)", "normal");
});

Emacs like binding in Insert Mode

require([
  'nbextensions/vim_binding/vim_binding',
], function() {
  // Emacs like binding
  CodeMirror.Vim.map("<C-a>", "<Esc>^i", "insert");
  CodeMirror.Vim.map("<C-e>", "<Esc>$a", "insert");
  //CodeMirror.Vim.map("<C-f>", "<Esc>lwi", "insert"); // This seems more likely to M-f (move forward one word)
  //CodeMirror.Vim.map("<C-b>", "<Esc>lbi", "insert"); // This seems more likely to M-b (move backward one word)
  CodeMirror.Vim.map("<C-f>", "<Esc>2li", "insert");
  CodeMirror.Vim.map("<C-b>", "<Esc>i", "insert");
  CodeMirror.Vim.map("<C-d>", "<Esc>lxi", "insert");
  CodeMirror.Vim.map("<C-h>", "<Esc>xi", "insert");
});

Move around in Insert mode with Ctrl-h/j/k/l

require([
  'nbextensions/vim_binding/vim_binding',
], function() {
  // Use Ctrl-h/l/j/k to move around in Insert mode
  CodeMirror.Vim.defineAction('[i]<C-h>', function(cm) {
    var head = cm.getCursor();
    CodeMirror.Vim.handleKey(cm, '<Esc>');
    if (head.ch <= 1) {
      CodeMirror.Vim.handleKey(cm, 'i');
    } else {
      CodeMirror.Vim.handleKey(cm, 'h');
      CodeMirror.Vim.handleKey(cm, 'a');
    }
  });
  CodeMirror.Vim.defineAction('[i]<C-l>', function(cm) {
    var head = cm.getCursor();
    CodeMirror.Vim.handleKey(cm, '<Esc>');
    if (head.ch === 0) {
      CodeMirror.Vim.handleKey(cm, 'a');
    } else {
      CodeMirror.Vim.handleKey(cm, 'l');
      CodeMirror.Vim.handleKey(cm, 'a');
    }
  });
  CodeMirror.Vim.mapCommand("<C-h>", "action", "[i]<C-h>", {}, { "context": "insert" });
  CodeMirror.Vim.mapCommand("<C-l>", "action", "[i]<C-l>", {}, { "context": "insert" });
  CodeMirror.Vim.map("<C-j>", "<Esc>ja", "insert");
  CodeMirror.Vim.map("<C-k>", "<Esc>ka", "insert");

  // Use Ctrl-h/l/j/k to move around in Normal mode
  // otherwise it would trigger browser shortcuts
  CodeMirror.Vim.map("<C-h>", "h", "normal");
  CodeMirror.Vim.map("<C-l>", "l", "normal");
  // Updated for v2.0.0
  // While jupyter-vim-binding use <C-j>/<C-k> to move around cell
  // The following key mappings should not be defined
  //CodeMirror.Vim.map("<C-j>", "j", "normal");
  //CodeMirror.Vim.map("<C-k>", "k", "normal");
});

Selecting all

require([
  'nbextensions/vim_binding/vim_binding',
], function() {
   CodeMirror.Vim.map("<C-a>", "ggVG", "normal");
});

Map Y to yy in insert mode

require([
  'nbextensions/vim_binding/vim_binding',
], function() {
   CodeMirror.Vim.map("Y", "yy", "normal");
});

Enable <C-c> mapping to exit insert mode

The default configuration adds a setting to ignore the chord. The following code removes that setting and provides a mapping.

// enable the 'Ctrl-C' mapping
// change the code mirror configuration
var cm_config = require("notebook/js/cell").Cell.options_default.cm_config;
delete cm_config.extraKeys['Ctrl-C'];
// change settings for existing cells
Jupyter.notebook.get_cells().map(function(cell) {
    var cm = cell.code_mirror;
    if (cm) {
        delete cm.getOption('extraKeys')['Ctrl-C'];
    }
});
// map the keys
CodeMirror.Vim.map("<C-c>", "<Esc>", "insert");

Run cell scrolls the cell to top (scroll output into view)

// Run and scroll to top
Jupyter.keyboard_manager.actions.register({
    'help': 'run selected cells',
    'handler': function(env, event) {
        env.notebook.command_mode();
        var actions = Jupyter.keyboard_manager.actions;
        actions.call('jupyter-notebook:run-cell', event, env);
        actions.call('jupyter-notebook:scroll-cell-top', event, env);
        env.notebook.edit_mode();
    }
}, 'run-and-top', 'vim-binding');

// Run and scroll to top and select next
Jupyter.keyboard_manager.actions.register({
    'help': 'run selected cells',
    'handler': function(env, event) {
        env.notebook.command_mode();
        var actions = Jupyter.keyboard_manager.actions;
        actions.call('jupyter-notebook:run-cell', event, env);
        actions.call('jupyter-notebook:scroll-cell-top', event, env);
        actions.call('jupyter-notebook:select-next-cell', event, env);
        env.notebook.edit_mode();
    }
}, 'run-and-next-and-top', 'vim-binding');

// Add two keyboard shortcuts for the new actions
require([
  'nbextensions/vim_binding/vim_binding',
  'base/js/namespace',
], function(vim_binding, ns) {
  // Add post callback
  vim_binding.on_ready_callbacks.push(function(){
    var km = ns.keyboard_manager;
    // Indicate the key combination to run the commans
    km.edit_shortcuts.add_shortcut('ctrl-enter', 'vim-binding:run-and-top', true);
    km.edit_shortcuts.add_shortcut('shift-enter', 'vim-binding:run-and-next-and-top', true);
    // Update Help
    km.edit_shortcuts.events.trigger('rebuild.QuickHelp');
  });
});

Show trailing spaces

// custom.js
require([
  'base/js/namespace',
  'notebook/js/cell',
  'codemirror/addon/edit/trailingspace'
], function(ns, cell) {
  var cm_config = cell.Cell.options_default.cm_config;
  cm_config.showTrailingSpace = true;

  ns.notebook.get_cells().map(function(cell) {
    var cm = cell.code_mirror;
    if (cm) {
      cm.setOption('showTrailingSpace', true);
    }
  });
});
/* custom.css */
.cm-trailingspace {
  border-bottom: 1px solid #ccc;
}

Adding custom actions

require(['codemirror/keymap/vim'], function() {
  // a
  CodeMirror.Vim.defineAction("hello", function(){console.log("hello")});
  // 'a' is the key you map the action to
  CodeMirror.Vim.mapCommand("a", "action", "hello", {}, {context: "normal"}); 
});

Adding custom operators

// custom operator for commenting
// (similar to commentary by Tim Pope)
// this woks with visual selection ('vipgc') and with motions ('gcip')
require(['nbextensions/vim_binding/vim_binding'], function() {
    CodeMirror.Vim.defineOperator("comment_op", function(cm) {
        cm.toggleComment();
    });
    CodeMirror.Vim.mapCommand("gc", "operator", "comment_op", {});
});

Calling a Jupyter shortcut

require(['base/js/namespace'], function(ns) {
    ns.keyboard_manager.actions.call('jupyter-notebook:run-cell-and-insert-below');
});

Using :q to leave Vim mode and re-enter Jupyter mode

require([
	'base/js/namespace',
	'codemirror/keymap/vim',
	'nbextensions/vim_binding/vim_binding'
], function(ns) {
	CodeMirror.Vim.defineEx("quit", "q", function(cm){
		ns.notebook.command_mode();
		ns.notebook.focus_cell();
	});
});

Use Ctrl + s to save in Vim normal and insert mode

require([
  'nbextensions/vim_binding/vim_binding',
  'base/js/namespace',
], function(vim_binding, ns) {
  // Add post callback
  vim_binding.on_ready_callbacks.push(function(){
    var km = ns.keyboard_manager;
    // Indicate the key combination to run the commands
    km.edit_shortcuts.add_shortcut('ctrl-s', 'jupyter-notebook:save-notebook', true);
    // Update Help
    km.edit_shortcuts.events.trigger('rebuild.QuickHelp');
  });
});

Define Ctrl+[ as a synonym for Esc (for leaving insert mode to normal mode, and leaving normal mode to Jupyter mode)

This synonym is a useful and popular way to reduce how far your hands must go from home row (see here and here). It is set up by default in most terminals, and for gVim, a mapping was set up specifically to reproduce the terminal behavior.

require([
  'nbextensions/vim_binding/vim_binding',
  'base/js/namespace',
], function(vim_binding, ns) {
  // Add post callback
  vim_binding.on_ready_callbacks.push(function(){
    var km = ns.keyboard_manager;
    // Indicate the key combination to run the commands
    km.edit_shortcuts.add_shortcut('ctrl-[', CodeMirror.prototype.leaveInsertMode, true);
    km.edit_shortcuts.add_shortcut('shift-ctrl-[', CodeMirror.prototype.leaveNormalMode, true);

    // Update help
    km.edit_shortcuts.events.trigger('rebuild.QuickHelp');
  });
});

Powerful resources