NeovimのLSPが動かない原因はプラグインではなくPATHだった

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-lspconfigroslyn_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_importsruff_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:pyrightruff
  • Go:gopls
  • Shell:bashls
  • JSON:jsonls

LSPが付かない時は、対象ファイルを開いて:LspInfoを見ます。そこで接続中のサーバー名とroot directoryを確認し、次に:checkhealth vim.lsp、最後にシェルでcommand -v <language-server>。いまはこの順番です。

今回もheadless起動でエラーがないことと、対象言語のテストファイルへ実際にLSPが接続することを分けて確認しました。Neovimが起動するだけでは、RoslynやRuffが期待した実行ファイルを使っている保証にはならないためです。

Neovimの黒い画面を格好よくしたい気持ちはあります。でも、その前に補完、定義ジャンプ、フォーマット、診断が静かに動いてほしい。土台が固まり、ようやくgdgrでコードを追うこと自体を楽しめるようになりました。

この環境をWindowsとWSL2へどう分けたかはAIにコードを書かせる時代に、WezTerm・WSL2・Neovimを整えた理由、実際にgdgrを使う流れはAIにコードを書かせた後、rg・fzf・Yazi・Neovimで読むへ書いています。

コメント

タイトルとURLをコピーしました