VisualStudioCodeのVim系キーバインドExtensionの実装方法

ちょっと古い話の読み物です。

Visual Studio Codeは最初から、Vimのキーバインドの実現が計画されていました。

2015年の11月、v0.10にて、Extensionのサポートが開始されました。

<img width="797" alt="Screen Shot 2018-12-08 at 17.45.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/3413/8d10988c-4384-663b-02ba-3e51da0ba929.png">

リリースと同時にいくつものExtensionがリリースされていることからも分かる通り、InsiderBuild版では既にExtension機能が追加されていました。確かこの時点でも既にExtensionに関するドキュメントがあったような気がします。

この時のVimキーバインドの実装方法は、すべてのキーにキーバインドを設定するというものでした。特にVimは、Ctrl、Cmdキーを使わずに、アルファベットや記号のキーに直接機能を割り振ることになります。私が作っていた[VimStyle](https://marketplace.visualstudio.com/items?itemName=74th.vimStyle)では以下のような実装がされていました。

```json:package.json
...
{
    "command": "vim.a",
    "key": "a",
    "mac": "a",
    "when": "editorTextFocus"
},
{
    "command": "vim.b",
    "key": "b",
    "mac": "b",
    "when": "editorTextFocus"
},
...
{
    "command": "vim.A",
    "key": "shift+a",
    "mac": "shift+a",
    "when": "editorTextFocus"
},
{
    "command": "vim.B",
    "key": "shift+b",
    "mac": "shift+b",
    "when": "editorTextFocus"
},
...
```

この実装方法では、問題がありました。`@`は`shift+2`とキーバインドが設定されており、JISキーボードでは異なるキーが割り当たります。キーバンドは、すべてUSキーボードとして設定することになっていました。

<img width="702" alt="Screen Shot 2018-12-08 at 18.02.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/3413/73ff97d4-7267-b79e-f470-40e77bbc2d2c.png">

VSCodeはChromeをベースにしてデスクトップアプリを作成できるElectronを使っています。キーの押下はよくあるonkeydownイベントで拾うことができ、そのイベントのkeyIdentifierというプロパティを使っていました。これを、キーボードのキーの名前に当てはめて、キーバインドを設定するようになっていました。

下記の記事にもある通り、JISキーボードとUSキーボードではkeyIdentifierが異なっていたり、重複していたりします。

[Chrome: keyIdentifierとOSとキーレイアウト](http://k2nr.me/blog/2011/09/30/chrome-keyidentifier.html)

私はkeyIdentifierベースでは問題があることがわかり、それをIssueで指摘したりしていました。Ubuntu、Windows、MacOSでkeyIdentifierが異なっていたりして、環境依存の実装を入れる必要があるのではないかと考えていました。

<img width="933" alt="Screen Shot 2018-12-08 at 18.06.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/3413/e0616584-e5cc-c380-36f5-54dcf3dbdf40.png">

これについて議論している中で、環境依存、キーボード依存のコードを入れるのは避けたいと思われたところで、native-keymapモジュールの利用が提案されました。

[native-keymap - npm](https://www.npmjs.com/package/native-keymap)

<img width="812" alt="Screen Shot 2018-12-08 at 18.25.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/3413/2e13e210-fb72-fa2f-04cb-f78522cdbd0a.png">

この実装により、キーバインドが設定できないことはなくなりましたが、US-keyboardベースでのキーバインドの設定は必要でした。私はJISキーボード向けオプションを用意していました。

この後もVimキーバインドExtensionに不満を持つ人はいて、VSCode本体でのVimキーバインドのサポートが望まれるような状態でした。

https://github.com/Microsoft/vscode/issues/3600

この議論のさなか、新たなAPIが実装されました。

<img width="792" alt="Screen Shot 2018-12-08 at 18.46.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/3413/f4973529-fe18-026d-6d82-4835dfc971d9.png">

このAPIは、VSCodeのすべてのキー押下イベントをExtensionに渡す、というものでした。registerCommandNiceというのは、ExtensionからVSCodeへのaddEventListenerのようなもので、このtypeというイベントをフックすると、args.textにキーの文字が入ります。

```javascript
let vim Ext = new VimExt();

registerCommandNice('type', function(args) {
  vimExt.type(args.text);
});
```

この強力なAPIにより、今までのキーバインドの問題は解決しました。

各VimキーバインドExtensionの作成者は、このキーバインドに対応させました。

ちなみに、私の作ったVimStyleは、自分の欲しかった機能は作り終えて、次に要望のあるnmap、nnoremapを実装して（やった）、nmapでVSCodeの機能を呼ぶためにExモードを実装しようとして（途中までやった）、設計が大変になって開発が止まっています。他の開発が盛んなVimプラグインを使いましょう。

おわり。

余談ですが、技術書典3でだした『VSCodeデバッグ技術』という本に、2018年現在に対応させて、RubyやGoogleAppEngineへのデバッグ方法を追加した本を出しました。

![vscode-small.jpg](https://qiita-image-store.s3.amazonaws.com/0/3413/19ae2fc7-c85c-f33f-1b44-83bfebcc5888.jpeg)

[定番コードエディタVSCodeの言語別デバッグ機能解説書！ 『Visual Studio Codeデバッグ技術』発行 技術書典シリーズ、12月の新刊 | インプレス R&D](http://www.impressrd.jp/news/181130/NP)