6500 行以上設定を書いた Neovimmer が VS Code に軸足を移す理由。

私は Neovimmer であり、かなりの時間を Neovim とともに過ごしてきた。dotfiles の Neovim の設定は 6500 行にものぼるらしい。 これでも使っていない built-in LSP 関連の設定をまるっと削除したり、ユーティリティを自作のプラグインとして切り出したりした後なので、昔より若干行数が減っているはずである。

$ tokei
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 JavaScript              2           55           50            2            3
 JSON                   12         2221         2221            0            0
 Lua                    53         6526         4975          963          588
 Markdown                1            3            0            2            1
 TOML                    2          676           66          478          132
 TypeScript              1            3            2            0            1
 Vim script              3          446          430            4           12
===============================================================================
 Total                  74         9930         7744         1449          737
===============================================================================

Neovim を使う熱意に関しては、自分でいうのもなんだが、人並みよりだいぶ強かったと思う。

VS Code + Dev Container が一世を風靡したときは、私は dockim というツールを作って Neovim を使い続けた。 このツールは Dev Container CLI のラッパーである。Neovim のセットアップを dockim build だけで終わらせるほか、Dev Container CLI に欠けているポート転送などの機能を補った。 dotfiles のセットアップスクリプトと連携して zsh のセットアップなども一括で終わらせるので、何なら本家 VS Code の Dev Container よりも便利まであった。

GitHub Copilot Chat が巷を席巻したときは、VS Code LM API それ自体を Neovim に持ち込んでやろうと思った。簡単なチャットが coc.nvim 上で行えるくらいまでにはなった。その後 Claude Code だったり Copilot CLI が出てきたので、結局は中途半端なところで放り出してしまったが。

しかし、そこには結局、ずっと目を背けてきた現実があった。 すなわち、私が私の理想とする開発環境に最も近いのは、今のところ Neovim ではなく VS Code なのではないか、ということだ。

理想の開発環境とは?

私にとっての理想の開発環境とは、それ一つですべてのニーズをかなえられる環境である。 本来エンジニアであれば、必要なところに適切なツールを投入して解決するほうが適切なのかもしれない。それでも私にとっての開発環境は道具というよりは家であったので、この家ですべてのことができるのかどうか、それが重要だった。 そして叶うなら、これからの自分に発生するニーズに対して、それを実装できるだけの柔軟な基盤があること。それらの拡張がプラガブルで直交していること。

今の世界でその環境に最も簡単に近づけるのは VS Code であるというのが最近の結論だ。

VS Code の何がそんなにすごいのか

自分が Neovim と大きく違いを感じているのは、主に以下の点である。

  1. GUI のクライアント + サーバーという理想的な分業
  2. JSON による宣言的で「動かない」設定と、API を通じた疎結合な拡張機能
  3. 圧倒的シェアのもとのエコシステム
  4. 大規模ファイルの取り扱いとパフォーマンス

1. GUI クライアント + サーバーの理想的な分業

いきなりターミナルエディタ愛好家からブーイングを受けそうなポイントだが、私個人としては、これだ。最も大きいのは。

Neovimmer でありながら、実は私はあまり TUI が好みではない。というよりも、GUI の方が必ず TUI よりもよい UI を達成可能だと考えている。 もちろん、特定の GUI がごちゃついていて、別の TUI の方がシンプルで使いやすい、ということは常に起こりうる。だが、それはあくまでも個別の UI デザインの問題である。tmux で切り替えが容易という意見もあるかもしれないが、それもウィンドウマネージャの問題である。 技術要素として見たとき、あなたが X11 も Wayland も入れていない Linux をデスクトップ利用しているのでなければ、UI を構築する点において GUI の方が優れているという点には同意してもらえると思う。極端な話、本当に TUI が理想なのであれば GUI で TUI を完全再現すれば少なくとも劣ることはない。

ただし、GUI が明確に弱い場面が存在する ‐ それがリモート接続である。この点においては、SSH するだけで利用可能になる TUI に軍配が上がる。

その当たり前の壁を貫通してきたのは VS Code (Remote Development) であった。

接続先がコンテナであろうと SSH であろうと、VS Code のクライアントは手元の GUI で動いている。一方、VS Code の拡張機能ホストを始めとしたコアはリモートサーバーで動いている。このため「リモートサーバー上で稼働する GUI を手元に持ってくる」ということが何も考えなくてもできてしまうのである。わかりやすい話が Markdown のプレビューアで、リモートサーバー上の Markdown ファイルのプレビューをローカルの VS Code から普通に見ることができる。私が TeX をコンテナ環境に作ったときは SyncTeX できるプレビューアのために何かを諦めるしかなかったというのに。

そうだ。もう一つ、細かいけれども強力な点の話をしておきたい。 GUI が手前で動いているため、コピー & ペーストについて何も考えなくて良いというのがある。Neovim では、手前で動いているのがターミナルエミュレータしかないため、コンテナで動いている Neovim で "+y するのは非常に困難だった。 tmux と OSC で頑張るというのも一つではあるが、結局エスケープシーケンスというやつはアプリケーションとターミナルマルチプレクサとターミナルとがすべていい感じにセットアップされていないと動かない。私は 24-bit color すら OS をまたぐとうまく動かないことがあり、到底安定した代物ではなかった。 前述の dockim では、クリップボードの中身を参照更新できる TCP サーバーをローカルに立て、コンテナ内の Neovim の g:clipboard にそのクライアントを設定することで対応していた。しかし、これはコピペの度に TCP 通信を行う必要があって重かったし、そもそも socat を使ったポート転送の hack を実装していなければ実現すらできない。その上、クリップボード暴露サーバーが listen しているというのには、いくら localhost に制限するとは言ってもセキュリティリスクも存在する。

一方で VS Code の GUI はフル機能の VS Code 実装を持った GUI クライアントである (ローカルで使えるのだから当たり前なのだが) 。そのため、その気になればクリップボードはおろか、フル機能の拡張機能を UI 側で動かすことさえできる。実際、VSCodeVim なんかは UI 拡張として設定されているので、Remote SSH で遠いサーバーとつながっているときでもスムーズにカーソルを動かすことができる。

何はともあれ、GUI があるということは、それだけ拡張と改善の余地、ひいてはエディタの潜在能力が豊かにあるということに等しい。 実際に GUI をふんだんに生かした Pahcer UI という拡張機能をつくっていたりもする。 この拡張機能についての詳細は Vim とは違う話なので割愛するが、とりあえず、グラフやホバー・リンクなど GUI の強みをふんだんに生かしたある種の可視化ツールである。いつか別記事で紹介しよう。

余談だが、単に GUI を持つだけであれば、Neovim GUI という類のソフトウェアが存在する。これを使うと上述のクリップボードの問題なんかは VS Code と同じように解決できたりする。 とはいっても、Neovim GUI は VS Code のそれに比べて遥かに薄く、せいぜいが Neovim 専用のターミナルエミュレータといったところだろう。 もちろん、Neovim 自体がサーバーとなり、それを可視化するフロントエンドを実装できるようなリッチな GUI を持つことが不可能なわけではない。個人的には OniVim などはかなり面白そうだった。OniVim 2 になって違う道に進んだまま Archive されてしまったけれど。 あるいは現代だと VSCode Neovim が一番その道に近いだろうか。

2. JSON による宣言的で「動かない」設定と、API を通じた疎結合な拡張機能

これは意見の分かれる話かもしれない。 実は、拡張機能を作り始めるだけであれば Neovim の方がよっぽど簡単だ。 プラグインマネージャでパスを通して init.lua から require すれば事足りる。 そもそも語弊を恐れずに言うと、Neovim においてはプラグインと設定に本質的な違いがない。 単に init.lua から外部ソースを source しているのと何ら変わりないので、プラグインでできることは init.lua に直書きしてもできる。 その自由度が Neovim であり、自分はその環境を長らく愛してきた (今も)。

一方で、それが悪く作用する部分もかなりある。 たとえばプラグインの設定をするというのは、Lua プラグインの世界では慣習的に setup() 関数を呼び出すことと同義である。しかし setup() 関数というのはただの関数なので、作者次第で様々な副作用を持つ。例えば二回呼び出したときの動作がどうなるかはプラグイン次第だし、特定のプラグインよりも前に setup() を呼んでいないとうまく動作しないこともある。

こうした状況が困るのは、例えばプロジェクト固有の設定をメンバー全員で共有したいケースである。

まずカレントディレクトリに .nvim.lua のようなファイルがあれば読む、ということはできる。 一応標準機能だけでもできたと思うが、私は nvim-config-local というプラグインを使っていた。 というのは .nvim.lua は Lua ファイルなので、ファイルの漏洩を含めて何でもできるスクリプトである。これを何の同意もなく勝手に実行されるのでは非常に危険だ。その点、前述のプラグインはファイルごとに信用するかどうか聞いてくれるようになっており、一度信頼してもハッシュ値が変わったら都度実行許可を求めるなど、最低限のガードレールが用意されていたためである。

ただ、仮にスクリプトを実行できたとしても、全員に効果のある .nvim.lua を書くことは正直不可能である。 例えばこのプロジェクトでは textwidth を 120 に設定したいと思ったとしよう。ここで vim.opt.textwidth = 120.nvim.lua に書いたとする。これはしかし、少なくとも私の個人環境では動かない。なぜなら私はそもそも init.lua で、ファイルタイプごとに textwidth を自動設定する設定を入れているからだ。グローバルな初期値は何も意味を持たないまま上書きされてしまう。

LSP の設定などはもっと共通化の需要が大きいと思うが、もっとややこしい話をしなければならない。Rust の inlay hint に関する設定をしたくなっても、lspconfig を使っているのか、個別の rustaceanvim などのプラグインを使っているのかで話は変わる。プラグインマネージャで lazy load は設定していますか? 他のプラグインが LSP の hook やハンドラを上書きしていませんか?

結局、私は設定を JSON で管理できるという理由で、coc.nvim を好んで利用していた。coc.nvim は VS Code にインスパイアされた補完エンジン、というよりはもはや拡張機能ホストであり、設定は .vim/coc-settings.json に書く形になっていた。これはまさに VS Code と同じ形式である。私は設定をすべてここに統一する覚悟を決め、coc-settings.json で Neovim 本体の設定も変更できるようにする coc 拡張 coc-vim-options を書いた。つまり、coc.nvim を使っていて拙作の coc 拡張を入れてくれる人に限っては、.vim/coc-settings.json"vim-options.textwidth": 120 と設定すれば設定を共有できるようにはなったわけだ。めでたしめでたし。ではない。

もう気づいてしまった。coc.nvim を気に入るということは、要するに、VS Code の選定が私に一番合っていたということだ。

設定はあえて「動かせない」JSON とすること。実行順序関係がないただの宣言的なデータにすること。これによって複数の設定ファイルのマージが機械的に可能になるし、誰が書いても同じ設定を適用できる。

拡張機能はあえて厳密にコアと分離すること。Vim の場合はプラグインは設定でもあったので、区別なくすべての機能にアクセスできた。一方で VS Code では、拡張機能は API を通してしかエディタにアクセスできない。それはビルトインコマンドと同じ動作をユーザー側で再現できないという歯がゆさとして現れることもあるが、でも、プラグインごとの設定やインストール状態をきちんと分離してエディタ側で管理できるという確かなメリットを生んでいる。

3. 圧倒的シェアのもとのエコシステム

VS Code が大企業 Microsoft にバックアップされた圧倒的シェアを持つエディタであることは、どれだけ言葉を尽くしてもあらがうことのできない事実である。 何らかのツールがリリースされたとして、オフィシャルに何かエディタ統合を実装するとなれば、まず VS Code から始めない理由がない。基本的に GitHub 上でオープンソースとして提供されることが多い Neovim のプラグインと異なり、Marketplace という統一された配布サイトで .vsix という専用形式のファイルを配る方式なので、企業からすればプロプライエタリな拡張機能も提供しやすい面はあるだろう。実際 Copilot Chat は当初クローズドソースで配られていた。 Pylance がオープンソース化してくれたらうれしいのにと思わないわけではないが、私は自由ソフトウェア主義ではないので、提供されないよりは提供されていた方がありがたいと思っている。

4. 大規模ファイルの取り扱いとパフォーマンス

意外なことに、大規模ファイルの取り扱いは VS Code の方がうまい。そのため安心してファイルを開くことができる。

というと若干語弊があって、Neovim も素の状態ではかなり軽量だったような気もする。しかし Neovim を素の状態で使うことはない。特に何の注意も払っていなければ LSP や tree-sitter などのシンタックスハイライトを有効にしていることが多い。 このタイミングでとんでもなく大きいログや JSON を開くとどうなるか? そう、固まるのである。こうなるともう Neovim を kill するしかない。さようなら、私の未保存の編集…。

VS Code の場合、まずは大規模ファイルは警告を表示してくれたり、長い行は表示を省略して壊れないように自分で気を遣ってくれる。しかも、警告にかかわらず表示したとして、編集ができなくなるくらい重たくなることはまれである。この点は本当に素直に感動した。VS Code は細かいところの作り込みがすごい。

思い返すと、VS Code の黎明期、同じく Electron を採用している Atom との比較をよく見かけた気がする。大規模ファイルの置換などでパフォーマンスを比較するような記事を見た記憶があるが、その記憶の中では、その時点でも圧倒的なパフォーマンス優位を誇っていた。このあたりも VS Code チームの最適化のたまものなんだろうな。例えば VS Code の最適化の一端を知りたい方はテキストバッファのリファクタリングを読んでいただくのがおすすめ。

ちなみに当然、Neovimmer なので、巨大ファイルに対して一部機能を停止するような仕組みを init.lua に設定することはした。 すなわち BufReadPre で、ファイルのサイズが一定以上である場合、syntax off を始めとして重そうな機能を可能な限り無効化することで軽量に操作できるようにするというものである。 ただ、それによってどれくらいの無効化ができるのかは結局プラグイン次第である部分が多く、たとえば coc.nvim はバッファ単位で処理を無効化することはできなかったので、coc.nvim 全体を停止させていた。それでも助かることがあるので残してあるが、お世辞にも使い勝手が良いとは言えない機能である。

結び

6500 行を超える設定を書いてきた Neovimmer として、Neovim の自由度と柔軟性は今でも魅力的だと感じている。しかし、開発環境に求めるものが「すべてのニーズを一つで満たせる家」であるならば、現時点では VS Code がその理想に最も近いと認めざるを得ない。

ただ、いずれにしても私の精神的な出身地が (Neo)vim であることは変わりないし、VS Code に移行してからも設定する営みはやめるつもりはない。別に既存の枠組みの中で小さく生きようと考えているわけではないのだ。

VS Code では、早速 Waltz というモーダル編集用の拡張機能を新たに作っている。この記事を含め日常利用しているが、かなり良い感じに使えている。 というか、本来はこの記事でその拡張機能の宣伝 (?) をするつもりだった。が、思ったよりも話が長くなってしまったので、回を改めることにした。

理想の開発環境は時代とともに変わる。これだけ言葉を並べておきながら、明日にはまた Neovim に戻っている可能性だってなくはない。