かみのメモ

コンピュータビジョン・プログラムな話題中心の勉強メモ

NeoVimユーザーがvscodeにも定住するためにやったこと

就活とか論文とか色々あり、実に半年ぶりの投稿です!

このブログでは以前、自分が使っているターミナル環境とエディタ環境を紹介しました。

kamino.hatenablog.com

今回の記事では、以前の記事で紹介したNeoVim環境に使用感の近いVisual Studio Code環境を構築した話を書いてみます。 表示系やコマンドモードなど細かい部分は詰め切れていないので、主にサイドバー(NERDTreeっぽく)、タブ移動、ターミナル周りを違和感なく使うためのショートカットの話を書いていきます。

f:id:kamino-dev:20200707153006p:plain
見た目はほぼデフォルトのまま

※ 2021/02/04: vim.use<C-v>で個別にctrlキーを有効化するオプションが削除されたようなので修正しました。またインサートモード中にctrl+h, ctrl+lを使いたいときのためにkeybindingのwhen条件を修正しました。

もくじ

スポンサーリンク

1. 動機

筆者は普段、コンピュータビジョン, 画像処理, 数値計算系のコードをC++ (CMake), CUDA, Pythonで書いています。 開発環境を行き来することが多いので、インストールが簡単かつリモート接続でも軽いNeoVimを愛用しているのですが、最近少し不満に思うところも出てきました。

IDEの環境構築に手間がかかる

以前の記事で紹介したように、コード補完やlintはプラグインを導入することで簡単に環境構築できます。

しかし、インタラクティブデバッグブレークポイントを挿入したり、変数の値を確認したり)を行うためのプラグインは、デバッガーと密に連携するせいか、素直にインストールできないことが多いです。

根本的な原因は、そうしたプラグインの需要が低いことかなぁと思っています。 そもそもNeoVimをエディタとして使う人は少数派でしょうし、その中で特定の言語のIDEプラグインをメンテしてくれる人はさらに希少です。

よく使うデバッグ環境を1度組むだけならいいのですが、「ちょっと他の言語を試したい」「他の開発環境を間借りしたい」というとき、環境構築に時間を取られるのは億劫です。

ターミナルの環境構築に手間がかかる

NeoVimはコマンドラインアプリなので、何らかのターミナルソフトウェアを通して利用するわけですが、意外と文字化けや色の不整合が起こります。 日本人は文字列やコメントにマルチバイト文字を利用するので、等幅フォントで表示されなかったり、カーソル位置がズレたり、特殊文字が化けたり、文字コードの問題で文字化けしたりというトラブルが起こりやすいです。

特に、Windows環境のターミナル(PowerShell, TeraTerm, Git Bash, cmderなど)は、フォントのせいなのかターミナルの作り込みが甘いのかはわかりませんが、この手のトラブルを頻発する印象があります。 最近はPowerShellが随分まともになってきているのと、Microsoftが新たにWindows Terminalというものを開発しているので、将来的には改善されるかもしれませんが。。。

一部の機能にはGUIを使いたくなる

NeoVimの欠点というよりVisual Studio Codeの長所ですが、SSH接続先の管理やDockerコンテナの一覧表示、マウスホイールによるスクロールなど、一部の機能はCLIよりGUIの方が便利なことがあります。


2020年現在、こうしたニーズを満たしつつマルチプラットフォームに使えるエディタといえばVisual Studio Code一択でしょう。 そんなわけで、できるだけNeoVimの使用感をキープできるVisual Studio Code環境にトライしてみました。

2. 表示関連の設定

まずは表示系の設定です。

とりあえず、折返しを有効にし、特殊文字を可視化し、行番号を相対表示にし、minimapを消しておきます。

{
    // 100文字を超える行は折り返して表示する
    "editor.wordWrap": "on",
    "editor.wordWrapColumn": 100,
    // 左端の行番号にカーソル位置からの距離を表示
    "editor.lineNumbers": "relative",
    // 行頭・文字間ともに空白を'・'で可視化する
    "editor.renderWhitespace": "all",
    // LF, CR, EOFなどの制御文字を可視化する
    "editor.renderControlCharacters": true,
    // minimapは表示しない
    "editor.minimap.enabled": false,
}

3. Vimプラグインを導入する

次に、vscode用のVimプラグインを導入します。

GitHub - VSCodeVim/Vim: Vim for Visual Studio Code

プラグインの詳細はREADMEで解説されているほか、vscodeのsettingsで各項目の説明が読めるので、そちらも参照してみてください。

3.1. 基本設定

キーバインドは、デフォルトのままでもほぼ問題なく使えます。

気を付ける点として、デフォルトではctrl+cctrl+vがコピー&ペーストになっているので、Vimキーバインドを優先させる設定が必要になります。 下の例では一括で設定してますが、"vim.use<C-d>": trueのように個別に設定することも可能です。アップデートでできなくなったようです。

また、コマンドモード(:から始まるやつ)のバックエンドに、Javascript実装ではなくNeoVim自体の実装を使うこともできます。 NeoVim実装に切り替えると、実行速度が若干速くなるほか、正規表現エスケープ方法や複数行マッチングの挙動が変わります(多分Javascript実装のバグなのでいつか修正されるかも)。

{
    // ctrl+cなどもVimキーバインドで上書きされる(macの場合はそもそもcommand+cなので変化なし)
    "vim.useCtrlKeys": true,
    // コマンドモードの動作にNeoVimを利用する(pathは環境に応じて調整)
    "vim.enableNeovim": true,
    "vim.neovimPath": "/usr/local/bin/nvim",
}

他にも、検索結果のハイライトの設定やeasymotionの有効化など色々な設定があるので、一度ドキュメントに目を通すことをおすすめします。

3.2. カスタムショートカット

Vimキーバインドプラグインを有効にすると、デフォルトで以下のショートカットが追加されます。

  • gd: カーソル下の要素の定義下に移動(開発言語のプラグインが必要)
  • gh: カーソル下の要素の定義情報が表示される(開発言語のプラグインが必要)
  • gq: 範囲選択されたコメントや長い文字列を適切な長さに改行し直す
  • gb: 繰り返し押すことで、カーソルのあたった単語と同じ単語が矩形選択されていく
  • af: visualモードに入ってから押すことで、(){}で囲われたブロックの内部が範囲選択される。繰り返すことで範囲が広くなっていく

これに加えて、前回の記事で紹介した個人的趣味によるキーバインド設定もやっておきます。 init.vim.vimrcの設定をJSON形式にするだけでokです。

{
    "vim.leader": "<space>",
    "vim.normalModeKeyBindingsNonRecursive": [
        { "before": [ "<leader>", "h" ], "after": [ "^" ] },
        { "before": [ "<leader>", "l" ], "after": [ "$" ] },
        { "before": [ ";" ], "after": [ ":" ] },
        { "before": [ ":" ], "after": [ "." ] },
        { "before": [ "." ], "after": [ ";" ] }
    ]
}

"after"の部分にはvscodeのコマンドも指定できるようです。 ただし、ここで設定したショートカットはあくまでVimキーバインドが有効なタブでしか使えません。

私の場合、タブ移動のショートカットは、画像ファイルのタブやsettingsタブなどVimキーバインドが無効なタブでも利用したいので、vscode自体のキーボードショートカット機能で設定することにしました。 ただし、ターミナルではctrl+lctrl+hが別のコマンドにマッピングされているので、それを邪魔しないように条件を指定しています。 同じく、vimのインサートモード中の動作も邪魔しないようにしています。

[
    // vim
    {
        "key": "ctrl+l",
        "command": "workbench.action.nextEditor",
        "when": "!terminalFocus && !sideBarFocus && vim.mode != 'Insert'"
    },
    {
        "key": "ctrl+h",
        "command": "workbench.action.previousEditor",
        "when": "!terminalFocus && !sideBarFocus && vim.mode != 'Insert'"
    },
 ]

4. サイドバーをNERDTreeっぽくする

次に、サイドバーをNERDTreeっぽく使うためのショートカットを設定していきます。

サイドバーを開きっぱなしにするかによって設定内容が若干変わりますが、ここでは「普段は隠しておいてファイル操作の間だけ開く」という想定で組んでいきます。 サイドバーを開きっぱなしにしたい場合は、以下の記事が参考になります。

qiita.com

なお簡易化のため、サイドバー>Explorer>ファイル一覧画面だけを利用することにし、開かれているファイルの一覧やGit, Dockerなどのツールバーにはショートカットを設定しないことにします。

ctrl+k, ctrl+sでサイドバーを開き、[j,k]で移動、oでフォルダを開閉し、m+[r,c,p,a,d,f]でファイル操作、tもしくはEnterでファイルを開くと同時にサイドバーを閉じる、という設定です。 ファイルを[s, o]で開く機能は、個人的に不要なので省いていますが、同じ要領で設定可能です。

{
    // カーソル移動をNERDTreeと同じかんじに
    "workbench.list.keyboardNavigation": "simple",
    // ファイルプレビューは無効化(開かれたファイルタブを永続化)
    "workbench.editor.enablePreview": false,
    "workbench.editor.enablePreviewFromQuickOpen": false,
    // macrosプラグインを使って、ファイルを開くと同時にサイドバーを閉じるコマンドを登録
    "macros": {
        "selectAndToggleSidebar": [
            "list.select",
            "workbench.action.toggleSidebarVisibility"
        ]
    },
}
{
    // explorer: toggle
    {
        "key": "ctrl+k ctrl+s",
        "command": "workbench.explorer.fileView.focus",
        "when": "!filesExplorerFocus"
    },
    {
        "key": "ctrl+k ctrl+s",
        "command": "workbench.action.toggleSidebarVisibility",
        "when": "filesExplorerFocus"
    },
    // explorer: open
    {
        "key": "Enter",
        "command": "macros.selectAndToggleSidebar",
        "when": "explorerViewletFocus && explorerViewletVisible && !explorerResourceIsFolder && !inputFocus"
    },
    {
        "key": "t",
        "command": "macros.selectAndToggleSidebar",
        "when": "explorerViewletFocus && explorerViewletVisible && !explorerResourceIsFolder && !inputFocus"
    },
    // explorer: move
    {
        "key": "Enter",
        "command": "list.toggleExpand",
        "when": "explorerViewletFocus && explorerViewletVisible && explorerResourceIsFolder && !inputFocus"
    },
    // explorer: rename
    {
        "key": "m r",
        "command": "renameFile",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
    // explorer: copy
    {
        "key": "m c",
        "command": "filesExplorer.copy",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
    // explorer: paste
    {
        "key": "m p",
        "command": "filesExplorer.paste",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
    // explorer: new file
    {
        "key": "m a",
        "command": "explorer.newFile",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
    // explorer: delete file
    {
        "key": "m d",
        "command": "deleteFile",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
    // explorer: new folder
    {
        "key": "m f",
        "command": "explorer.newFolder",
        "when": "explorerViewletVisible && filesExplorerFocus && !inputFocus"
    },
}

5. 下部パネルのショートカットを調整する

最後に、下部パネルまわりのショートカットの設定です。

f:id:kamino-dev:20200707154608p:plain
vscodeの下部パネル

NeoVimでは、ターミナルをタブとして開くことができますが、vscodeでは下部パネルの一部として実装されています。 ターミナルをタブ化するアイデアもあったらしいですが、「画面が乱雑になる」「どうしてもやりたい場合はtmux使え」という意見により却下されたようです(https://github.com/microsoft/vscode/issues/10546)。 NeoVimと操作感の差ができてしまいますが、なるべく慣れやすいショートカットを組むことにしましょう。

下部パネルには、TERMINALのほかにもPROBLEMS, OUTPUT, DEBUG CONSOLEのパネルが存在します。 これらのパネルにも移動できた方が楽なので、以下のようにショートカットを組んでみました。

  • ctrl+space, ctrl+space: 下部パネルを開閉
  • ctrl+space, ctrl+[p, o, d, t]: 各パネルにフォーカス(下部パネルが閉じているときは開いた上でフォーカス)
  • ctrl+space, ctrl+n: 新しいターミナルを追加する
  • ctrl+space, ctrl+w: 現在のターミナルを削除する
  • ctrl+space, ctrl+[h, l]: ターミナルを移動する

ctrl+spaceを活用するのは、NeoVimではまず使わないキーだからです。 頭の中でNeoVimとvscodeの使い勝手を区別できるなら、好きに設定していいと思います。

ちなみに、terminalからファイルを開くとき(NeoVimのnvr --remote-tabに相当)はcode <filename>でokです。

{
    // panel(bottombar): toggle
    {
        "key": "ctrl+space ctrl+space",
        "command": "workbench.action.togglePanel"
    },
    // panel(bottombar): focus
    {
        "key": "ctrl+space ctrl+t",
        "command": "workbench.panel.terminal.focus"
    },
    {
        "key": "ctrl+space ctrl+d",
        "command": "workbench.debug.action.focusRepl"
    },
    {
        "key": "ctrl+space ctrl+o",
        "command": "workbench.panel.output.focus"
    },
    {
        "key": "ctrl+space ctrl+p",
        "command": "workbench.panel.markers.view.focus"
    },
    // terminal: new
    {
        "key": "ctrl+space ctrl+n",
        "command": "workbench.action.terminal.new"
    },
    // terminal: move
    {
        "key": "ctrl+space ctrl+l",
        "command": "workbench.action.terminal.focusNext",
        "when": "terminalFocus"
    },
    {
        "key": "ctrl+space ctrl+h",
        "command": "workbench.action.terminal.focusPrevious",
        "when": "terminalFocus"
    },
    // terminal: kill
    {
        "key": "ctrl+space ctrl+w",
        "command": "workbench.action.terminal.kill",
        "when": "terminalFocus"
    },
}

以上、簡単に自分の環境を解説してみました。

使い勝手のいい設定を見つけたら、また追記したいと思います。