Neovimを「解体」せよ:nvim-lspconfigを捨て、LuaネイティブでLSPを制御する狂気と快楽
多くのエンジニアが `nvim-lspconfig` に依存し、巨大なエコシステムの中に安住している。だが、真に生産性を極めるのであれば、我々は「ツールに使われる側」から「ツールを制御する側」へと脱却しなければならない。
今日は、あえて標準的なラッパーを排し、Neovimの `vim.lsp.start()` APIを直接叩くことで、「自分の脳内モデルと完全に同期する超軽量LSPクライアント」を構築するアーキテクチャについて語ろう。
—
なぜ「nvim-lspconfig」を捨てるのか?
`nvim-lspconfig` は素晴らしい抽象化レイヤーだが、ブラックボックスでもある。チーム開発において、「なぜかLSPが起動しない」「特定の設定が上書きされている」というトラブルに遭遇したことはないか?
自分で `vim.lsp.start()` を書けば、以下のメリットが手に入る。
1. 起動コストの極小化: 読み込まれるLuaコードが数行で済むため、エディタの立ち上げ速度が物理的に高速化する。
2. 完全な可観測性: 通信のすべてをフックできる。特定の言語に必要なオプションだけを明示的に渡すため、余計な挙動が一切混入しない。
3. デバッグの容易性: 問題が起きた際、ライブラリのソースを追う必要はない。自分が書いた数行のコードを疑うだけで済む。
—
実装:最小構成のLSPクライアント
まずは、特定のプロジェクト専用の `lsp.lua` を作成し、それを `init.lua` から読み込む構成を推奨する。
— lua/custom/lsp.lua
local function start_minimal_lsp()
— 現在のバッファに対してLSPプロセスを直接起動する
local client_id = vim.lsp.start({
name = “my-minimal-ls”,
cmd = { “pyright-langserver”, “–stdio” }, — 使用するLSPサーバーの実行コマンド
root_dir = vim.fs.root(0, { “.git”, “pyproject.toml” }), — プロジェクトルートの自動検知
capabilities = vim.lsp.protocol.make_client_capabilities(),
settings = {
python = {
analysis = {
typeCheckingMode = “basic”, — 型チェックの厳密度を制御
},
},
},
})
— 接続が確立された瞬間に特定のキーバインドを注入する
if client_id then
vim.api.nvim_create_autocmd(“LspAttach”, {
callback = function(args)
local opts = { buffer = args.buf, silent = true }
— 実務で最も使用する「定義移動」と「ホバー」を極限まで最適化
vim.keymap.set(“n”, “gd”, vim.lsp.buf.definition, opts)
vim.keymap.set(“n”, “K”, vim.lsp.buf.hover, opts)
end,
})
end
end
— Pythonファイルを開いた時にのみ実行するトリガー
vim.api.nvim_create_autocmd(“FileType”, {
pattern = “python”,
callback = start_minimal_lsp,
})
この実装の核心は、LSPの状態を特定のプロセスに完全に委ねている点だ。大規模な開発環境において、LSPが暴走してメモリを食いつぶす現象を、このアプローチなら `vim.lsp.stop_client(client_id)` 一発で制御下に置ける。
—
チーム開発で「設定を腐らせない」ためのアーキテクチャ
個人の設定をいくら磨き込んでも、チーム全体が同じ体験を共有できなければ意味がない。私が現場で採用しているのは 「Dotfilesのセグメント化」 だ。
設定ファイルの構造ベストプラクティス
`init.lua` はエントリーポイントとして機能させ、設定は言語や目的ごとにJSON/YAMLで外部化する。
.config/nvim/
├── init.lua # 最小限のプラグインローダーのみ
├── lua/
│ ├── core/ # グローバルなUI設定
│ └── lsp_configs/ # 言語ごとの設定ファイル (JSON)
│ └── python.json # JSONで宣言的に記述
python.json の例:
{
“server_name”: “pyright”,
“root_patterns”: [“.git”, “setup.py”],
“feature_flags”: {
“completion”: true,
“diagnostics”: true
}
}
この構成により、チームメンバーは `init.lua` をいじることなく、JSONを書き換えるだけでLSPの挙動を揃えることができる。これは、ジュニアエンジニアが誤ってエディタ環境を破壊するリスクをゼロにする最強のガードレールだ。
—
現場で震えるほど役立つ「隠れたキーコマンド」
LSPを自作すると、自分の指先とエディタが直結する。以下のキー操作は、私が必ず設定する「生産性の劇薬」だ。
1. `vim.lsp.buf.code_action()` のマッピングを `
- LSPが提案するリファクタリングを0.5秒以内に適用する。これなしで現代のコードは書けない。
2. `vim.diagnostic.setloclist()`:
- 警告やエラーを場所リスト(Location List)に送る。複数のエラーがある際、`:lnext` で遷移するスタイルは、IDEのGUIペインよりも圧倒的に速い。
3. `vim.lsp.buf.format({ async = true })`:
- 保存時に自動フォーマットせず、あえて明示的なホットキー `
f` に割り当てる。大規模リファクタリング時に、diffが汚れるのを防ぐプロの防衛策だ。
—
結論:技術的負債を「技術的資産」に変える
既存のプラグインに頼り切りになると、我々はツールの「仕様」の中でしか生きられなくなる。しかし、`vim.lsp.start()` を用いてLSPを制御することは、エディタを「自分の手足」に改造する行為そのものだ。
君のNeovimは、誰かの設定のコピーではない。君自身の思考速度を最大化するための、唯一無二のインタフェースであるべきだ。
さあ、`init.lua` を開き、不要なプラグインを一行ずつ削除し、君自身のアーキテクチャを構築したまえ。その先にこそ、真の「エンジニアの自由」が待っている。