Neovimを一瞬でVSCode並みに便利にする

去年8年ぶりに vimrc を書き直した時はLSPの体験があんまりよくなくてLSPなしでNeovimを使い続けていたのだが、様々な言語のOSSをメンテする都合で用途に応じてIntelliJVSCodeとNeovimの三刀流で暮らしていた結果、可能ならNeovimに寄せたいけどそれならLSPを使いたいなということになり、今回LSPの所を真面目に設定し直して、かなり良い体験になっている。

正直Neovimの設定はVSCodeのそれに比べたら面倒なんじゃないかという印象がありサボっていた節があるが、実際にやってみるとVSCodeと同程度に簡単に済む方法もあったので紹介したい。

何故Neovimなのか

LSPの話の前に、タイトルだけ見た人がそもそも単にVSCode使えばいいじゃんと言いそうなので、どうしてIntelliJVSCodeではなくNeovimに揃えようと思ったのかについて書いておく。

IntelliJの不便なところ

JetBrainsのIDEにはRubyとCを両方正式にサポートしているIDEがなく *1 、CRuby開発者的には大変不便である。やるならRubyMineとCLionをいったり来たりしないといけないが、C-なんとかでウィンドウ切り替えをしている都合、エディタに2つもショートカットを使うのは正直勘弁願いたいという状態である。仮にショートカットの問題がなかったとしても、一窓で完結できるVSCodeやNeovimに比べて体験が劣るのは変わらないと思う。

それがモチベーションでVSCodeを使い始めたのだが、KotlinやRubyIntelliJやRubyMineの方がVSCodeより圧倒的に言語サポートが充実している *2 ので、できればIntelliJにCをサポートしてもらうのが一番望ましいのだが、そうなりそうな見込みはない。

次に困るのが、Vimのタブに相当するものがないこと。Vimはタブの中で画面を分割し、分割された窓の中で更に複数バッファを持つという感じなのだが、IntelliJではトップレベルで画面を分割し、その中で複数バッファを持つという感じなので、VimのバッファのようなものはあるがVimのタブはないという感じになっている。Vimのタブは同じファイルの別の場所のコンテキストを保存しながら作業するといった用途に便利で、これがないのは大変辛いのだが、まあこれもIntelliJに入ることはないだろう。

Vimキーバインドユーザー的にはIdeaVimの挙動のVim互換性がイマイチなのも気になるところだが、VSCodeVimやVSCode Neovimがあまりにも厳しいので、最近はIdeaVimの方がマシだなという評価になってきた。そもそもVimやNeovimを使ってしまった方が早いというのはそう。

VSCodeの不便なところ

Kotlinを書く会社をやめてRubyとCを同時にいじるCRuby開発をする会社に入社したので、仕事で使うIDEIntelliJからVSCodeにシフトしたのはごく自然なことなのだが、個人的にはJetBrainsのIDEの方がVSCodeより便利だなあと思うシーンの方が多いので、VSCodeが業界で一番人気ぽい雰囲気なのは個人的にはやや意外である。

VSCodeで一番困っているのは、エディタで改行した時の自動インデントの開始位置がかなり異常なことで、VSCodeVimはデフォルトのVSCodeのインデント挙動に従うのでこの不便をもろに受けてしまう。具体的には、以下のように空行にカーソルがある時、

def foo
  bar
[ここ]
  baz
end

僕は空行にはスペースを残さない派なのでインデントがないところに静止するが、そこから改行すると次の行でもインデントなしで始まってしまう。ここではbazと同じインデント位置から次の行を始めたいわけだが、そうならない。他にも気になる挙動がかなりいっぱいある。そこで便利なのがVSCode NeovimというVSCodeをNeovimフロントエンドとして使うプラグインなのだが、これを使うと度々NeovimとVSCodeの同期が壊れたっぽくなって不便というのはおいておいて、VSCodeVimで存在するインデントの問題は解消するものの、それとは全然違うところでNeovimとは異なる不思議なインデントの挙動をする。何故Neovimと同じ挙動をしてくれないのかよくわからないが、これが理由でVSCodeを使うのが辛くなってしまった。

IntelliJのところに書いたVimのタブに相当するものがない問題はVSCodeにも同様に存在する。これがVSCodeの辛さに拍車をかけている。VSCodeではこれはやらないと明確に宣言されているので、絶望である。

Neovimの不便なところ

この中では一番便利だと思っているが、公平性のためにも(?)、自分がNeovimに感じている不満についても書いておこうと思う。

僕はNeovimの利点の一つがターミナル上で起動してtmuxのウィンドウの1つにしたりできることだと思っていて、CUIからしか起動しないのだが、そのためターミナルが表現できるUIしか使えないという欠点がある。これまでそれほど気になることはなかったが、LSPのdiagnosticsでエラーが出るところに赤い波線ではなく文字と同じ下線しかいれられないとか、これくらいはそんなに頻繁に使わないからショートカットで効率化するんじゃなくてマウスで操作してもいいんだけどなみたいのがそもそも選択肢として存在しなくて、僕が乱暴に消費しまくっているショートカットキーと衝突している機能を使う必要が出てくる。とはいえ、いずれも大した問題ではない。

Neovimの二大LSPプラグイン

やっとLSPの話。vim-jpを眺めていると、設定の仕方が二大派閥みたいになっていて、僕は両方完全に設定を終えてみた上で片方を選ぶという風にしたので、違いについて書いておく。

nvim-lspconfig

そもそもNeovimにはビルトインのLSPクライアント nvim-lsp があるのだが、nvim-lspconfigはそれの設定だけ提供するというもの。大雑把に言うと以下のような特徴がある。

  • nvim-lspconfigがサポートしているLSPの幅が広い
  • デフォルトの設定がややイケてないので、設定に時間がかかる
    • デフォルトだとdiagnosticsがインラインで全て展開されるが、情報量多すぎて見にくいので、カーソル合わせたところだけ出すようにしたくなる (設定は可能)
    • fidget.vim いれたり lualine.nvim のカスタマイズなどで自分からprogress出すようにしないと使いにくい (設定は可能)
    • Language Serverのインストールもmason.nvimとかが必要

次の奴に比べるとこちらの方がカスタマイズが効いてVimっぽいので、Vim過激派(?)の人はこちらを好みそうだなという感じはある。

coc.nvim

それとよく対比されるものとしてcoc.nvimがある。"Make your Vim/Neovim as smart as VSCode" というキャッチコピーなのだが、これは単にLSPを使っているというよりは実際にVSCodeプラグインを移植して来ることで動いている。そのためプラグインVim側ではなくnode.js側のエコシステムで管理することになり、設定もJSONに書くところを含め、何かとVimというよりはVSCodeぽい感じの挙動をする。以下のような印象がある。

  • VSCodeでメジャーな拡張に相当するものは大体あるが、nvim-lspconfigに比べると少ない
  • あまり設定しなくてもいい感じになる
    • いい感じのポップアップUIで進捗が出たり、Language Serverのインストールもやってくれたり、最初からVSCodeぽくなっている

例えばcoc.nvimだとRubyのLanguage ServerがSolargraphしか対応してないのだが、nvim-lspconfigでいろいろ試した結果Solargraphが一番よくできていると思ったので、正直設定に時間をかける余裕がそれほどないというのもあり、coc.nvimでいいかという感じになった。

タイトルの「Neovimを一瞬でVSCode並みに便利にする」というのはどうやるかというと、つまり忙しい人はcoc.nvimを使っておけということである。nvim-lspconfigの方の設定は何行にも渡って設定が必要だったが、coc.nvimの方はインストールしておきたいプラグインの列挙とキーバインドの設定だけでほぼ使える状況になった。実際にはstatuslineに coc#status() を出したり nvim-scrollbar でdiagnosticsの分布がパッと分かるようにしてみたりしたが、definitionに飛んだりcompletion出したりするみたいな用途ではデフォルトでほぼ困らない。

ちなみにこの記事で主にVimではなくNeovimの話をしているのは、このプラグインNeovimじゃないと動かないからだ (編集: 名前の通り当初はNeovim限定だったものの、今はVimでも動くらしい *3 )。Vim 9はVim 9 Scriptを発明するみたいなことをやってるが、まあ皆Vim 9専用言語よりLuaとかNode.jsとかPythonを書きたくて、長期的にNeovimの方がエコシステムが発達していくんじゃないかと思っている。

各言語のLSP

まだcoc.nvimを使い始めて日が浅いので、直近使っている数少ないプラグインについてしか言及しないが、ここで書いてない奴も例えばGoとかはgoplsの奴使って大体VSCodeと同じになりそうだなという印象しかないので、Wikiで各言語のプラグイン名を調べてインストールするだけで特に困ることはなさそう。とはいえ、せっかくなので既に使った奴に関しては感想を残しておく。

C

coc-clangdとcoc-cclsを試したが、どっちも変わらんなと思ったのでclangdをいれるだけで良いcoc-clangdを使っている。CRubyだとclangdがfalse positiveなdiagnosticsをまあまあ出してくるが、それはVSCodeデフォルトのvscode-cpptoolsでも同じなので、まあC言語を使っているのが悪いということで諦めている。

vscode-cpptoolsに相当する奴は移植するつもりがcocに移植するつもりが今のところないらしいが、コンパイラgccを使っていようがclangを使っていようがclangdの体験は大して変わらないので、coc-clangdでよさそう。むしろvscode-cpptoolsは何か知らないけどエディタ起動するだけで勝手にconfigureとかビルド始めてビルドディレクトリを壊されるのが気になっていて、clangdに移行して良かったという感じがある。

追記: 最初書き忘れてた内容として、CはVimデフォルトのシンタックスハイライトが弱いので、coc-settings.jsonsemanticTokens.filetypes でセマンティックシンタックスハイライトを有効にすると便利。あとclangdの設定としてcompile_commands.jsonが必要なんだけど、これはbearを使って makeのかわりに bear makeとすると良い。これをやる時は1からビルドする状態でmakeを叩く必要がある。

Rust

VSCodeと同じでrust-analyzerを使う。おしまい。rust-analyzerはVSCodeでもNeovimでもファイルを保存した時に実行されるので、いくつかのdiagnosticsはエディタ上で修正しても保存をもう一度叩くまで警告されっぱなしになったりするのだが、良くも悪くもVSCodeとNeovimはこれに関しては似たような体験になる。IntelliJやCLionでは保存しなくてもdiagnosticsが直るような自然な挙動になっていた気がするが、どうもJetBrainsはrust-analyzerは一部のコードを共有しているだけで、Rust対応が基本スクラッチになっているようで、他の場所では素のrust-analyzerに比べ微妙ぽい挙動をしていた記憶 *4 があり、Rustに関してはどのエディタが特別体験が良いとかはなさそう。

Ruby

Rubyは自分で型を書く場合はSorbetSteepを使っておくと、LSPが割といい感じになるらしいのだが、僕は仕事とは別に最近CRubyの標準ライブラリを秘かに書き続けていて、ruby/rubyのトップレベルにおもむろにGemfileを置いたりするわけにはいかないので、別途型情報がなくても自明な範囲で定義ジャンプやdiagnosticsがやりたいなという気持ちがあった。

SorbetやSteepを使わない場合に関してはRubyMineが圧倒的に体験がよくて、これはLSPでは太刀打ちできない感じがあり、普通の人は基本的にRubyMineを使っておけば良いと思っている。その一方でRubyとCを両方RubyMineからいじれないという問題があるので、CRuby開発者やC拡張gemメンテナでかつ複数エディタを行ったり来たりしたくない人は、LSPで耐える必要がある。

その範囲ではSolargraphが現状一番よくできているというか、定義ジャンプに使うという用途では選択肢が実質これしかない。他のものは textDocument/definition がそもそも提供されてなかったり、それがexperimentalになっていて有効化しても起動時に壊れたり、解析対象がものすごく小さくないと動かなかったりする。SolargraphはSolargraphでまあまあ対応してないシンタックスがあって、じゃあと思ってPR送っても割とPR来てもマージしてなさそうな雰囲気だったりとか、それができなければSolargraphに忖度したコード () を書くはめになったりとか、RBSも未対応だとか、かなり先が長い感じがする。同僚やr7kamuraさんとかがその辺に対応した別のものを開発しそうな気配を感じており、応援している。

Rubyは自分がコミッターをやっているのもあってIDEなくても書けるやろみたいな慢心をしていた節があり、RBSで型付けをしたりLSPを使って開発体験向上みたいなのもつい最近までほとんど興味がなかったのだが、実際に使ってみると、今のSolargraphのクオリティのものでもかなり開発効率が上がったように感じる。一方でSorbetやSteep以外のLSPの現状最高の体験がこれかあ、というのは他の言語に比べると相当遅れている印象なので、このあたりがRuby 3の注力分野の一つになっているのはとてもいいことだと思った。

DAP

デバッガもVSCodeのCodeLLDBみたいな奴使いたいんだよなーと思って調べたところ、vimspectorというのを使うとVSCodeのデバッガ拡張をNeovimに使えるらしい。というのも、LSPみたいな感じでMicrosoftDAP (Debug Adapter Protocol)というものを提唱しているようで、coc.nvimとvimspectorを使うと、何かVSCodeの拡張は大体Neovimでも使えそうだなという雰囲気がある。まだこれは試せてないのでまた今度。

まとめ

coc.nvimをいれてここからプラグインを探して使うだけで割とVSCodeみたいな体験になる。

*1:https://www.jetbrains.com/help/idea/discover-intellij-idea.html に "C/C++ are not officially supported in IntelliJ IDEA, but you can use CLion" と書いてある

*2:Kotlinに関しては同じ会社が作っている以上は、JetBrainsが作っているIDEが一番充実しているという状態は揺るがないと思う

*3:ycinoさんに教わりました、感謝。一方、最初Neovimにしか対応してなかったことからも、この段落で言ってることは依然としてそれほど外してないように思うので、そのまま残した。

*4:unused importの警告がないとかだったような気がするけど、うろおぼえ。それは最新版では直ってたりするかも