VSCodeでもVimのキーバインドが使いたい

# はじめに

VSCodeでVimのキーバインドを使いたいと思ったときに、最初に候補に上がってくるのがVSCodeVimではないだろうか？
しかし、Vimとは違う点も多く、vimrcとの連携も一部しか対応しておらず、気になったところが出ては修正、という作業が頻発していた。
`setting.json`を整理する目的で、設定内容を振り返る。

:::note warn
Mac向けの内容となります。ご了承ください。
:::

# キーバインドの設定

leaderを好きなキーに割り当てる

```jsonc
{
  "vim.leader": "<space>", // 筆者はスペースキーにしている
}
```

## ノーマルモード

ノーマルモードのキーバインドは`vim.normalModeKeyBindingsNonRecursive`で設定できる

```jsonc
{
  "vim.normalModeKeyBindingsNonRecursive": [

    // xのときはレジスタに入れない
    {
      "before": ["x"],
      "after": ["\"", "_", "x"]
    }, 
    // sのときはレジスタに入れない
    {
      "before": ["s"],
      "after": ["\"", "_", "s"]
    },

    // US配列でコマンドモードに入りやすいように":"と";"を入れ替えている
    {
      "before": [";"],
      "after": [":"]
    },
    {
      "before": [":"],
      "after": [";"]
    },

    // Neovimで変更されたように"Y"を"D"とか"C"みたいな感じでつかいたい（デフォルトだとVimと同様に"yy"の挙動）
    {
      "before": ["Y"],
      "after": ["y", "$"]
    },

    // デフォルトの"H"と"L"は使わないので別のものを割り当てる
    // ^は押しづらいので"H"で行頭
    {
      "before": ["H"],
      "after": ["^"]
    },
    // $は押しづらいので"L"で行末
    {
      "before": ["L"],
      "after": ["$"]
    },

    // <Leader> + h,j,k,l でウィンドウ移動
    {
      "before": ["<Leader>", "h"],
      "after": ["<C-w>", "h"]
    },
    {
      "before": ["<Leader>", "j"],
      "after": ["<C-w>", "j"]
    },
    {
      "before": ["<Leader>", "k"],
      "after": ["<C-w>", "k"]
    },
    {
      "before": ["<Leader>", "l"],
      "after": ["<C-w>", "l"]
    },

    //定義を見る
    {
      "before": ["["],
      "commands": [
        {
          "command": "editor.action.peekDefinition"
        }
      ]
    }, 

    //水平に開く
    {
      "before": ["<Leader>", "s"],
      "commands": [":sp"]
    },
    //垂直に開く
    {
      "before": ["<Leader>", "v"],
      "commands": [":vs"]
    },

    // インデント
    // ">>"を">"にしている
    {
      "before": [">"],
      "commands": ["editor.action.indentLines"]
    },
    // "<<"を"<"にしている
    {
      "before": ["<"],
      "commands": ["editor.action.outdentLines"]
    },

    // タブ移動関係
    // 左のタブに移動
    {
      "before": ["<C-l>"],
      "after": ["g", "t"]
    },
    // 右のタブに移動
    {
      "before": ["<C-h>"],
      "after": ["g", "T"]
    },
    // 左のタブに移動（"gt"に対して"gT"は押しづらいので"gr"にしている）
    {
      "before": ["g", "r"],
      "after": ["g", "T"]
    },

    // 現在の行の下に空の行を挿入
    {
      "before": ["<Leader>", "o"],
      "after": ["o", "<ESC>"]
    },
    // 現在の行の上に空の行を挿入
    {
      "before": ["<Leader>", "O"],
      "after": ["O", "<ESC>"]
    },

    // 検索結果に遷移したとき画面中央に表示する
    {
      "before": ["n"],
      "after": ["n", "z", "z"]
    },
    {
      "before": ["N"],
      "after": ["N", "z", "z"]
    },
    {
      "before": ["*"],
      "after": ["*", "z", "z"]
    },
    {
      "before": ["#"],
      "after": ["#", "z", "z"]
    },

    // マルチカーソル
    // 選択した単語と同じ次の単語にカーソル追加
    {
      "before": ["<C-n>"],
      "after": ["g", "b"]
    },
    // 現在位置の真上にカーソル追加
    {
      "before": ["<C-k>"],
      "commands": [
        {
          "command": "editor.action.insertCursorAbove"
        }
      ]
    },
    // 現在位置の真下にカーソル追加
    {
      "before": ["<C-j>"],
      "commands": [
        {
          "command": "editor.action.insertCursorBelow"
        }
      ]
    }
  ],
```

## インサートモード

インサートモードのキーバインドは`vim.insertModeKeyBindings`で設定できる。

```jsonc
{
  "vim.insertModeKeyBindings": [
    // サジェストを発火させる
    {
      "before": ["<C-m>"],
      "commands": ["editor.action.triggerSuggest"]
    },

    //
    {
      "before": ["v", "L"],
      "after": ["$", "h"]
    },

    // BackSpaceの代わりに<C-h>を使っているので、<C-l>で右側を消せるようにしている
    // <C-h>に対しては<C-d>のほうが直感的かも
    {
      "before": ["<C-l>"],
      "after": ["<Right>", "<BackSpace>"]
    }
  ],
}
```

## ビジュアルモード

ビジュアルモードのキーバインドは`vim.visualModeKeyBindingsNonRecursive`で設定できる。

```jsonc
{
  "vim.visualModeKeyBindingsNonRecursive": [

    // 左端に移動
    {
      "before": ["H"],
      "after": ["0"]
    },
    // 右端に移動
    {
      "before": ["L"],
      "after": ["$", "h"]
    },

    // インデント
    // ">>"を">"にしている
    {
      "before": [">"],
      "commands": ["editor.action.indentLines"]
    },
    // "<<"を"<"にしている
    {
      "before": ["<"],
      "commands": ["editor.action.outdentLines"]
    },

    // マルチカーソル
    // 選択した文字に対してカーソル追加
    {
      "before": ["<C-n>"],
      "after": ["g", "b"]
    },
    // 複数行選択時、各行末にカーソル追加  
    {
      "before": ["<C-l>"],
      "commands": [
        {
          "command": "editor.action.insertCursorAtEndOfEachLineSelected"
        }
      ]
    }, 
  ],
}
```
# その他の設定

キーバインド以外の設定を紹介する

# Vimと共通する設定
```jsonc
{
  // 検索で同じ文字列に色がつくやつ
  "vim.hlsearch": true,

  // 検索時には大文字小文字を無視
  "vim.ignorecase": true,

  // 入力中でもマッチしたものを表示してくれる
  "vim.incsearch": true,

  // 行の最初と最後でのカーソル移動時、行送りできるようにする
  "vim.whichwrap": "h,l,<,>,[,]",
}
```

## VSCodeVim独自の設定
```jsonc
{
  // クリップボードをレジスタとして使う
  "vim.useSystemClipboard": true, 

  // ビジュアルモードのとき"*"で検索できるようにする
  "vim.visualstar": true,

  // 指定したコマンドで、VSCode（またはVSCodeVim）の機能を使わないようにする
  "vim.handleKeys": {
    "<C-d>": true // <C-d>はVSCodeVimのものを使う
  },
}
```

## インサートモード終了時に日本語入力を無効にする

細かい設定は https://github.com/VSCodeVim/Vim#input-method このあたりを参照していただきたい。

ざっくりいうと、
brewでim-selectをインストールする

```shell
brew tap daipeihust/tap && brew install im-select
```

インストール後に

```shell
which im-select
```
で表示されたpath（たぶん`/usr/local/bin/im-select`になっている）を`vim.autoSwitchInputMethod.obtainIMCmd`に指定する。

ターミナルから`im-select`を実行することで現在選択されているIMEのkeyが分かるので、そのkeyをもとに、下記のような設定を追加する

```jsonc
{
  "vim.autoSwitchInputMethod.enable": true,
  "vim.autoSwitchInputMethod.defaultIM": "com.google.inputmethod.Japanese.Roman",
  "vim.autoSwitchInputMethod.obtainIMCmd": "/usr/local/bin/im-select",
  "vim.autoSwitchInputMethod.switchIMCmd": "/usr/local/bin/im-select {im}",
}
```

筆者はGoogle日本語入力を使用しているので、"com.google.inputmethod.Japanese.Roman"[^1]になっている。
[^1]: Google日本語入力では、入力ソースの「英数」が`"com.google.inputmethod.Japanese.Roman"`で、「ひらがな」が`"com.google.inputmethod.Japanese.base"`になっている。

# VSCodeVimの便利な機能

VimっぽいけどVimじゃない要素だったり、メジャーなVimプラグインを模した機能の一部を紹介します。

## vim-easymotion

`vim.easymotion`を`true`にすることで機能がオンになる

詳しい使い方は調べていただきたいが、画面上のカーソル移動がめっちゃ便利になる。
Vimを使っていて縦の移動がつらいと感じるときは、これを使うと幸せになれるかも

https://github.com/VSCodeVim/Vim#vim-easymotion

## vim-indent-object

テキストオブジェクトのようにインデント単位でも操作できるようになる。
例えば、`dii`とするとインデントの内側の内容は全て削除される。

## CamelCaseMotion

`vim.camelCaseMotion.enable`を`true`にすることで機能がオンになる。
`w`,`e`,`b`を`<leader>w`,`<leader>e`,`<leader>b`とすることで、キャメルケースやスネークケースの単語を分割して扱える。

## 独自のキーマッピング
`gh`: マウスをホバーしたときと同じように、エラーメッセージや型定義を表示できる。
`gd`: 定義箇所に遷移する（command+clickと同じような感じ）

このあたりは、VSCodeのショートカットキーでも設定可能だが、VSCodeVimではデフォルトで上記が割り当てられている。

# 使わないほうが良さそうな機能

## vim-airline

`vim.statusBarColors`の設定でステータスバーの色が変更できるが、動作が遅くなるのでおすすめしない。

# VSCodeVimのイマイチなところ

## `Command+Z`と`u`の挙動が違う

これは、どっちも使うから良くないんだろうけど、Command+ZとVSCodeVimの`u`の挙動が違うので、たまに何をやってるかわからなくなる
Command+Zで元に戻すときに前にやってた`u`が発火して、どの状態にも戻れなくなるときがある。
使うのはどちらかに統一しよう。

## 立ち上がりが遅い

起動してから使えるようになるまでが遅い。
環境要因かもしれないが、行数が多いファイルだと、更に遅くなる気がする。

# まとめ

ここまでVSCodeVimの設定を晒してきたが、前述したように、VSCodeVimは重くて起動時に1秒くらい待たないと使えないという致命的な欠点がある。
VSCode Neovimであれば、設定もほぼなくNeovimのキーバインドが使える。
NeovimユーザーはVSCode Neovimを使おう。

自分はプラグインが干渉してしまっていたので、`init.vim`を下記のように編集するだけで使えた。

```vim
if !exists('g:vscode')
  " VSCodeでは読み込まないpluginなど
endif
```
# 参考
https://github.com/VSCodeVim/Vim