Neovimの標準LSP設定(lsp/とvim.lsp.enable)
NeovimでLSPを使うと言えば長らくnvim-lspconfigを入れる話でした。プラグインマネージャを入れ、nvim-lspconfigを入れ、require('lspconfig').gopls.setup{}と書く。この手順が当たり前すぎて、そもそもNeovim本体がどこまでやってくれるのかを意識する機会はほとんどありませんでした。0.11で入ったlsp/ディレクトリとvim.lsp.enable()によって、この構成はプラグインなしで組めるようになっています。設定ファイル1枚と1行の呼び出しで言語サーバーが立ち上がります。
検証環境:macOS上のNeovim 0.12.4と、言語サーバーにgopls v0.16.2(Go 1.23.5)を使って確認しました。掲載しているログ・診断メッセージ・:checkhealthの出力はすべて実行結果の実物です。プラグインは1つも入れていない状態で試しています。Go以外の言語サーバーでは検証していないため、filetypesやroot_markersの具体値は各サーバーのドキュメントで確認してください。
| 要素 | これだけ覚える |
|---|---|
~/.config/nvim/lsp/{名前}.lua | サーバー1つにつき設定を1枚置く |
vim.lsp.enable('{名前}') | その設定を有効にする |
:checkhealth vim.lsp | 接続できているか確認する |
| grn gra grr | リネーム・コードアクション・参照(標準割り当て) |
設定ファイルを1枚置く
Neovimは起動時にruntimepath上のlsp/ディレクトリを見て、そこにあるLuaファイルを言語サーバーの設定として扱います。ファイル名がそのままサーバーの名前になります。goplsならlsp/gopls.luaを作り、設定テーブルをreturnします。
-- ~/.config/nvim/lsp/gopls.lua
return {
cmd = { 'gopls' },
filetypes = { 'go', 'gomod' },
root_markers = { 'go.mod', '.git' },
}
書く項目は3つで足ります。cmdが起動するコマンド、filetypesがどのファイルタイプで起動するか、root_markersがプロジェクトのルートを判定する目印です。root_markersに挙げたファイルを上位ディレクトリへ遡って探し、最初に見つかった場所がそのサーバーの作業ルートになります。
あとはinit.luaで有効化するだけです。
-- ~/.config/nvim/init.lua
vim.lsp.enable('gopls')
-- 複数まとめて渡すこともできる
-- vim.lsp.enable({ 'gopls', 'lua_ls' })
これで*.goを開いた瞬間にgoplsが起動して接続します。手元の隔離環境(プラグインゼロ)でproj/main.goを開いてクライアント一覧を取ったところ、次のように返りました。
client=gopls id=1 root=/.../lsptest/proj
root_markersに書いたgo.modのあるprojディレクトリがルートとして選ばれています。vim.lsp.enable()は「このファイルタイプならこの設定で起動する」という対応づけを登録するだけで、実際の起動はバッファを開いたときに遅延して行われます。
動いているか確かめる
接続の確認は:checkhealthが最短です。:checkhealth vim.lspを実行すると、有効になっている設定と実際に接続中のクライアントが分けて表示されます。手元の出力から関係する部分を抜き出したものがこれです。
vim.lsp: Active Clients ~
- gopls (id: 1)
- Root directory: /.../lsptest/proj
- Command: { "gopls" }
- Attached buffers: 1
vim.lsp: Enabled Configurations ~
- gopls:
- cmd: { "gopls" }
- filetypes: go, gomod
- root_markers: { "go.mod", ".git" }
この2つのセクションの見分けが切り分けの肝になります。Enabled Configurationsにしか出ていない場合は設定は読めているが接続まで至っていない状態で、原因はfiletypesの書き間違いか、root_markersが見つからないか、cmdのコマンドがパスに無いかのどれかです。Active Clientsに出ていれば接続自体は成功しています。
診断が出るかどうかも見ておきます。存在しない関数を呼ぶGoファイルを開いてvim.diagnostic.get(0)を読むと、goplsからのエラーがそのまま入っていました。
diag line=4 sev=ERROR msg=undefined: undefinedFunc
診断の表示自体はNeovimの標準機能なので、追加のプラグインは要りません。行間に文字を出したい、記号を変えたいといった見た目の調整はvim.diagnostic.config()で行います。
キーマップは最初から用意されている
「LSPを入れたらキーマップを大量に書く」という印象があるかもしれませんが、主要な操作は0.11以降のNeovimに標準で割り当て済みです。素のnvim --cleanでmaparg()を引いて、実際に定義されているものを確認しました。
| キー | 動作 | 割り当て |
|---|---|---|
| grn | シンボルのリネーム | 標準(グローバル) |
| gra | コードアクション | 標準(グローバル) |
| grr | 参照の一覧 | 標準(グローバル) |
| gri | 実装へジャンプ | 標準(グローバル) |
| gO | ファイル内のシンボル一覧 | 標準(グローバル) |
| ]d [d | 次/前の診断へ移動 | 標準(グローバル) |
| K | ホバー(型や説明の表示) | サーバー接続時にバッファ単位で設定 |
Kだけ扱いが違います。素のNeovimで引くと未定義ですが、goplsが接続したバッファで同じことをすると定義済みになっていました。Vim本来のKはヘルプ引きなので、LSPが付いたバッファでだけ意味が上書きされる作りです。
同じくバッファ単位で差し替えられるオプションもあります。接続後のGoバッファで確認したところ、こうなっていました。
omnifunc=v:lua.vim.lsp.omnifunc
tagfunc=v:lua.vim.lsp.tagfunc
'omnifunc'が差し替わっているので、挿入モードでCtrl-x Ctrl-oを押すとLSPの補完候補が出ます。Vim標準の補完の枠組みをそのまま使い回している形です。'tagfunc'も同様で、Ctrl-]のタグジャンプが定義位置へのジャンプとして機能します。「LSPを入れたのに補完もジャンプも既存のキーのまま使える」のはここが理由です。
nvim-lspconfigは要らなくなったのか
結論から言うと、役割が変わっただけで存在意義は残っています。Neovim本体はlsp/を読み込む仕組みを提供しているだけで、各サーバー向けの設定そのものは同梱していません。手元のNeovim 0.12.4のランタイムを見るとlsp/ディレクトリ自体が存在しませんでした。
goplsのようにcmdとfiletypesとroot_markersを書けば済むサーバーなら自分で書くほうが速いのですが、初期化オプションが複雑なサーバーや、ルート判定に癖のあるサーバーは自力で調べると骨が折れます。nvim-lspconfigは現在その「サーバーごとの設定ファイル集」としてlsp/形式のファイルを配る役割に寄っていて、入れるとvim.lsp.enable('サーバー名')を呼ぶだけで済むようになります。
| やること | 自分で書く | nvim-lspconfigを使う |
|---|---|---|
| 設定の入手 | lsp/名前.luaを書く | プラグインが同梱している |
| 有効化 | vim.lsp.enable('名前')(どちらも同じ) | |
| 向いている場面 | 使う言語が1〜2個 | 言語が多い・設定が複雑 |
言語サーバーを2つ3つしか使わないなら、vim.packでプラグインを1つ入れるよりもlsp/に2枚書くほうが構成としては軽くなります。逆に、業務で5言語も6言語も触るならnvim-lspconfigに任せたほうが調べ物の時間を減らせます。どちらを選んでもvim.lsp.enable()を呼ぶ点は共通なので、あとから乗り換えるのも難しくありません。
検証用のGo環境をプラグインゼロで組んでみて、いちばん驚いたのが設定ファイルの短さでした。4行のLuaと1行のenableだけで、定義ジャンプも診断もリネームも動きます。つまずいたのはfiletypesの指定で、最初{ 'go' }とだけ書いていてgo.modを開いたときに何も起きず、しばらく設定を疑っていました。:checkhealth vim.lspでEnabled Configurationsには出ているのにActive Clientsが空、という表示を見て、接続前の段階で止まっていると分かったのが解決の糸口でした。この2セクションの見比べは覚えておく価値があります。
まとめ
Neovimの標準LSPは、~/.config/nvim/lsp/にサーバーごとの設定を1枚置いてvim.lsp.enable()を呼ぶ、という2ステップで動きます。cmd・filetypes・root_markersの3つを書ければgoplsのようなサーバーはそれだけで接続し、診断もgrnなどのキーマップも追加設定なしで使えます。うまく動かないときは:checkhealth vim.lspでEnabled ConfigurationsとActive Clientsのどちらまで進んでいるかを見ると原因の範囲が一気に狭まります。nvim-lspconfigは不要になったのではなく、サーバー設定の配布元という役割に移っただけなので、扱う言語の数で使い分けるのが現実的です。