Jupyter notebookのショートカットをカスタマイズしてsublime text風にする


Jupyter Notebookでsublime textのショートカットを使用する方法を紹介します。

行の複製、複数選択、複数行同時編集といったsublime textでの便利な機能を使用できます。
また、ショートカットの割り当てを変更することもできます。

Jupyter Notebookとは

Pythonのコードと実行結果をまとめて保存できるエディタです。
公式サイトのトップページで雰囲気をつかめます:Project Jupyter | Home

Jupyter Notebookのショートカットには2種類ある:「エディタのショートカット」と「コマンド実行」のショートカット

Jupyter notebookでは、コードの塊(セル)を実行したり、セルを追加すると言った機能があります。
これらの機能のショートカットをカスタマイズする方法は、公式で詳しく説明されています: Customize keymaps — Jupyter Notebook 6.0.3 documentation

以下のように、ショートカットカスタマイズ用のファイルを作成することでカスタマイズできます。

~/.jupyter/nbconfig/notebook.json
{
  "keys": {
    "command": {
        "bind": {
            "G,G,G":"jupyter-notebook:restart-kernel-and-run-all-cells"
        }
    }
  },
}

また、GUIで直感的にショートカットを変更することもできます。

しかし、エディタでのコード編集に使用するショートカットについては、解説があまり充実していないように思います。

この記事では、エディタ・ショートカットのカスタマイズ方法を説明します。

以下の方法により、コード編集用のショートカットをカスタマイズでき、例えばsublime textのショートカットを使用できるようになります。

custom.jsでjupyter notebookでのJavaScriptの動作をカスタマイズ

jupyter notebookで読み込まれるJavaScriptファイルの中には、ユーザーがカスタマイズするのを想定したものがあります。
それがcustom.jsです。

これは、デフォルトでは配置されていませんが、存在する場合には自動的に読み込まれます。

~/.jupyter/custom/custom.jsに、ファイルを作成してください。OSによって配置場所がことなるかもしれないので、

import jupyter_core
jupyter_core.paths.jupyter_config_dir()
# この結果が、'~/.jupyter'になるはずです。
# その場合、'~/.jupyter/custom/custom.js'が読まれます。

で確認してみてください。

このcustom.jsに以下のように書きます。
jupyter notebookのJavaScriptでは、モジュールの管理にrequirejsが使用されているので、その構文に整合するように書きます。

.jupyter/custom/custom.js
define([
    "custom/js_required/import-sublime-keybindings",
    "base/js/events"
], function(keybindings, events) {
    "use strict";
    keybindings.bindSublimeKeymap();
});

外部のファイルにて、以下のようにショートカット設定を書き、custom.jsで読み込みます。

.jupyter/custom/js_required/import-sublime-keybindings.js


define([
  'base/js/namespace',
  'notebook/js/cell',
  'codemirror/lib/codemirror',
  'codemirror/keymap/sublime'
], function(IPython, cell, CodeMirror) {
  "use strict";

  var bindSublimeKeymap = function() {

    var map = CodeMirror.keyMap.sublime;
    var notebook = IPython.notebook;
    if (!notebook) return;


    // 既存のショートカットを無効化する関数
    var deleteIfExist = function(strCommand) {
      if (map[strCommand]) {
        delete map[strCommand];
      }
    };

    // ここでキーマップをsublime textに設定
    cell.Cell.options_default.cm_config.keyMap = 'sublime';

    // Cmd-Enterのショートカットを無効化する
    deleteIfExist("Cmd-Enter");

    // sublime textでデフォルトで用意されていないショートカットを自分で用意する
    map["Shift-Cmd-D"] = "deleteLine";
    map["Cmd-D"] = "duplicateLine";
    map["Alt-W"] = "wrapLines";
    map["Cmd-B"] = "selectNextOccurrence";
    map["Shift-Cmd-M"] = "selectBetweenBrackets";
    map["Alt-Up"] = "swapLineUp";
    map["ALt-Down"] = "swapLineDown";


    // 以上で新規セルにショートカットが設定される。
    // 既存セルにも適用するために以下を実行
    var cells = IPython.notebook.get_cells();
    var numCells = cells.length;
    for (var c = 0; c < numCells; c++) {
      cells[c].code_mirror.setOption('keyMap', 'sublime');
    }

  };

  return { bindSublimeKeymap: bindSublimeKeymap };

});

これでJupyter notebookを再起動すれば、ショートカットが適用されるはずです。