NeovimのLSPが動かない。そんな時、私はまた別のプラグインを探しかけました。補完が弱ければ追加、C#が動かなければ追加。やっているうちに、何が起動しているのか余計に分からなくなります。
今回詰まったのは、プラグインの数ではありませんでした。Roslynはどの.NETを使うのか。Ruffはどこから来たものか。WSL2のLSPがWindows側の実行ファイルを見ていないか。問題はPATHでした。
そこで、既存のLazyVim設定は残しました。追加する前に実行経路を整理し、C#、TypeScript、Python、Go、Shell、JSONでLSPが接続するところまで確認する。SQLは無理にLSPを足さない。これが今回の結論です。
先に、誰がコマンドを用意するか決めた
同じコマンドを複数の道具から入れないよう、担当を決めました。
| 対象 | 担当 |
|---|---|
| mise | .NET SDK、Node.js、Go、Python本体 |
| Mason | Roslyn Language Server、netcoredbg、Bash Language Serverなど |
| uv | Pythonプロジェクトの環境、Ruff |
| Conform | フォーマッターの呼び出し |
| nvim-lint | リンターの呼び出し |
| LazyVim | プラグイン読込と既存のキーマップ基盤 |
例えばRuffはuvで管理しているものを使い、Masonからは追加しませんでした。入っていれば安心、ではなく、どちらが起動するかを一つに絞ります。
nvim-lspconfigの公式ドキュメントでも、まず実行コマンドがPATH上にあるかを確認します。設定を増やす前に、ここを見るべきでした。
C#はOmniSharpとRoslynを同時に起動しない
LazyVimの.NET用extraを有効にすると、OmniSharpの設定が入ります。今回はRoslyn Language Serverへ寄せました。
同じC#ファイルへ二つのLanguage Serverを付ける必要はありません。OmniSharpは消さず、起動だけ止めて戻せるようにしました。roslyn.nvimも使わず、nvim-lspconfigのroslyn_lsを設定しています。
さらに、mise管理の.NETはシステム標準とは別の場所にあります。Roslynの子プロセスまで同じSDKを使うよう、Neovim側でDOTNET_ROOTとPATHを明示しました。
cmd_env = {
DOTNET_ROOT = "<mise-managed-dotnet-root>",
DOTNET_ROOT_X64 = "<mise-managed-dotnet-root>",
PATH = "<mise-managed-dotnet-root>:<mason-bin>:" .. vim.env.PATH,
}
nvim-lspconfigのRoslyn設定も、実行ファイルをPATHから見つける構成です。私の環境では、miseの.NETをRoslynの子プロセスまで渡す必要がありました。
ShellとPythonも、暗黙のPATHに任せなかった
WSL2にはWindows側のPATHも混ざります。Bash Language Serverが意図しない実行ファイルを選ばないよう、Masonの実体とNode.js本体をPATHへ明示しました。
Pythonでは、Ruffのコマンドを~/.local/bin/ruffまたはPATH上のRuffから解決する設定にしました。formatterはConformからruff_organize_importsとruff_formatを呼び、Shellはshfmt、lintはShellCheckを使います。
やったことは、便利なプラグインの追加ではありません。「このコマンドを使う」と決めただけです。
TypeScript、Go、JSONは既存設定を活かせた
TypeScriptのvtsls、Goのgopls、JSONのjsonlsは既存設定のまま接続できました。問題が出ていない言語まで触ると、直した範囲がぼやけます。この三つは接続確認だけにして、設定を増やしていません。
SQLの汎用LSPは無理に採用しなかった
SQL用にsqlsも試しました。しかしGo経由でのビルドが長時間終わらず、実行可能ファイルができない状態でした。
SQL対応の欄を埋めるためだけに、別のLSPを足すのはやめました。現時点ではTreesitter、SQLFluff、DB操作用UIを残し、汎用SQL LSPは保留です。
使えないものを設定へ残すと、後から見た時に「導入済みなのか、壊れているのか、意図的に外したのか」が分からなくなります。採用しなかった判断も設定の一部だと思っています。
起動できることと、実際に付くことを分けて確認した
設定後は、Neovimのheadless起動だけでは終わらせませんでした。C#、TypeScript、Python、Go、Shell、JSONの各テストファイルを開き、対象のLSPが接続することを確認しました。
確認できた構成は次のとおりです。
- C#:
roslyn_ls - TypeScript:
vtsls - Python:
pyrightとruff - Go:
gopls - Shell:
bashls - JSON:
jsonls
LSPが付かない時は、対象ファイルを開いて:LspInfoを見ます。そこで接続中のサーバー名とroot directoryを確認し、次に:checkhealth vim.lsp、最後にシェルでcommand -v <language-server>。いまはこの順番です。
今回もheadless起動でエラーがないことと、対象言語のテストファイルへ実際にLSPが接続することを分けて確認しました。Neovimが起動するだけでは、RoslynやRuffが期待した実行ファイルを使っている保証にはならないためです。
Neovimの黒い画面を格好よくしたい気持ちはあります。でも、その前に補完、定義ジャンプ、フォーマット、診断が静かに動いてほしい。土台が固まり、ようやくgdやgrでコードを追うこと自体を楽しめるようになりました。
この環境をWindowsとWSL2へどう分けたかはAIにコードを書かせる時代に、WezTerm・WSL2・Neovimを整えた理由、実際にgdやgrを使う流れはAIにコードを書かせた後、rg・fzf・Yazi・Neovimで読むへ書いています。

コメント