🔌

Neovim の LSP 関係のプラグインの役割

2023/06/23に公開


この記事は、vim 駅伝 2023/06/23 の記事です。

前回: 【企画】これだけは外せない!あなたにとっての「いぶし銀」な Vim プラグインを教えてください
次回: 6/26 の予定


自分が過去にかなり迷ったので、Neovim を本格的に使い始めたい、あるいは vim-lsp や coc.nvim から移行したい方へ残しておきます。

Neovim の builtin LSP の導入方法の記事を見たらインストールしておくべきプラグインの導入方法が書かれているのを見て、

結局、このプラグインって何のために入れてるの?

と思った方向けの記事のつもりです。

この記事では、以下の4つのプラグインについて扱います。

  • nvim-lspconfig
  • mason.nvim
  • mason-lspconfig.nvim
  • null-ls.nvim

builtin LSP について

現在の Neovim には、標準で LSP (Language Server Protocol) クライアントが備わっています。
これは、LS (Language Server) の起動・リクエストの送信や、レスポンスの処理などを簡単に行えるようにするためのものです。
この辺りの具体的な使い方は @ryoppippi さんの記事が分かりやすいと思います。

https://zenn.dev/ryoppippi/articles/8aeedded34c914

依存関係

今回扱うプラグインの中では

  • mason-lspconfig.nvim の mason.nvim、nvim-lspconfig への依存

以外、依存関係はありません。

  • nvim-lspconfig
  • mason.nvim
  • null-ls.nvim

これらの三つは別々に使うことができ、nvim-lspconfig、mason.nvim の両方を使っている場合にmason-lspconfig.nvim があったら簡単に設定ができる、というような関係です。

また、LSP を利用するプラグインも、mason.nvim、null-ls.nvim への依存はほぼないです。
nvim-lspconfig への依存は、特定の LS の機能強化のためのプラグイン(e.g. typescript.nvimflutter-tools.nvim、...)でない限りしていないと思います。

プラグインの役割

nvim-lspconfig

「ほとんどの場合は、LSP の設定はこんな感じにするだろうな~」というものを集めたもの

シンプルに言うと設定集です。

ryoppippi さんの記事から引用させていただくと、builtint LSP の関数で lua-language-server を起動させたい場合、以下のように書くことができます。

vim.api.nvim_create_autocmd('FileType', {
    pattern = 'lua',
    callback = function()
        vim.lsp.start({
            name = 'lua_ls',
            capabilities = vim.lsp.protocol.make_client_capabilities(),
            cmd = { 'lua-language-server' },
            root_dir = vim.fs.dirname(vim.fs.find({ '.git' }, { upward = true })[1]),
        })
    end,
})

vim.lsp.start() の引数の中身を補足しておきます。

  • name
    LSP 内部で使われる名前で、LS を識別するのに使われる。
  • capabilities
    LS が提供する機能を示すもので、LS がどの機能を実装しているかを調べるときや、 LS に対して機能を指定するときなどに使われる。
  • cmd
    LS を起動するコマンド、または RPC クライアントを返す関数。上の例だと lua-language-server という名前のバイナリ、あるいは起動用のスクリプトに PATH が通っていれば起動できる。
  • root_dir
    プロジェクトルート
  • ......

これらの値が vim.lsp.start_client() に渡されて[1]起動します。

ただ、このままだと Git リポジトリしか root_dir を正しく判定できませんし、場合によってはリポジトリルートとプロジェクトルートが異なっていたり、デフォルトの capabilities を変更した方がよかったりと、一つの LS を追加するだけで多くの労力を要します。

nvim-lspconfig は、様々な LS のいい感じの設定を用意し、自分ですべてを設定するという手間を無くしてくれます。

require("lspconfig").lua_ls.setup({})

例えば、上記のような設定をした場合、以下の default_config に色々処理を加えられたものが vim.lsp.start_client() に渡されます。

https://github.com/neovim/nvim-lspconfig/blob/4a69ad6646eaad752901413c9a0dced73dfb562d/lua/lspconfig/server_configurations/lua_ls.lua

mason.nvim

LS、debugger、formatter、linter とかのインストールを簡単にするもの

いわゆるパッケージマネージャです。
色々なツールを特定の箇所にダウンロード・インストールし、デフォルトでは PATH を通してくれます。

逆に言えば、元々インストールされているもの (e.g. deno、rustfmt、...) は、ここでインストールする必要はありません[2]

また、下記のようなトラブルもたまにあるので、別の方法でインストールすることも視野に入れておくといいと思います。

https://zenn.dev/kyoh86/articles/67047e3b18139a

mason-lspconfig.nvim

mason.nvim でインストールされている LS を、nvim-lspconfig で自動セットアップするもの

nvim-lspconfig を使用した場合、使用する LS それぞれに対して setup() を呼ぶ必要があります。

local mason = require('mason')
local lspconfig = require('lspconfig')

mason.setup()

-- Lua_LS
lspconfig.lua_ls.setup({})
-- pyright
lspconfig.pyright.setup({})
-- vtsls
lspconfig.vtsls.setup({})
-- rust-analyzer
require('rust-tools').setup({})
......

また、設定された LS を見つけられなかった場合は警告を送ってくるので、 nvim-lspconfig 内部で使われている判定を通してから setup() を呼ぶ、あるいは LS の追加・削除をするときに設定も編集する必要があり、面倒です。

https://github.com/neovim/nvim-lspconfig/blob/ae3debca7ce4f081452345ba4c0443ecf4dec99e/lua/lspconfig.lua#L40

そんな時に mason-lspconfig.nvim を使って以下のようにすることで、mason.nvim でインストールされている LS 全てを一括で処理することができます。

local mason = require('mason')
local lspconfig = require('lspconfig')
local mason_lspconfig = require('mason-lspconfig')

mason.setup()
mason_lspconfig.setup()
mason_lspconfig.setup_handlers({
    function(server_name)
        lspconfig[server_name].setup({})
    end,
    rust_analyzer = function()  -- 個別に設定することもできる
        require('rust-tools').setup({})
    end,
    ......
})

また、

  • mason.nvim と nvim-lspconfig で使用している LS の名前が異なっている場合の処理
    • mason.nvim: lua-language-server
    • nvim-lspconfig: lua_ls
  • LS の自動インストール

も行ってくれます。

null-ls.nvim

LSP に対応していないツールを LS like に動かすもの

LSP には様々な機能が定義されていてます。

  • エラー箇所の指摘
  • コードのフォーマット
  • 補完
  • Code Action
  • ......

しかし、LSP ではないものの、これらの機能を提供するツールもあります。formatter や linter がその最たる例でしょう。

例として clippy (Rust の linter) を挙げてみます。

このように、cargo clippy を実行すると lint の結果が表示されます。しかし、この出力を直接 Neovim で使うことはできません。

これを解決するために、出力結果からデータを取得し、それを LS の機能として使えるようにするのが null-ls.nvim です。

波線が引かれている場所を Diagnostics として、修正案が書かれている部分を Code Action として提供することで、rust-analyzer (Rust の LS) の出した Diagnostics や Code Action と一緒に扱うことができます。

そうすることで、LSP というエコシステムを利用する他のプラグインと統合できたり、自分が設定の中で扱うのも容易になります。

その他

僕が使ってないので説明はできないものの、あると便利っぽいプラグインを紹介しておきます。

  • mason-null-ls.nvim
    mason.nvim と null-ls.nvim を統合してくれる。
  • neoconf.nvim
    VS Code の .vscode/settings.json 的なことができるようになる。
    プロジェクトローカルな LS の設定とかもできる。


終わり

- documents -

https://neovim.io/doc/user/lsp.html

https://github.com/neovim/neovim/tree/master/runtime/lua/vim/lsp

脚注
  1. vim.lsp.start()vim.lsp.start_client() を薄くラッピングしたもので、buffer の指定や LS の使い回しがしやすくしてあります。 ↩︎

  2. mason.nvim を使わずに元々インストールしていたものを使用する場合は mason-lspconfig.nvim が自動で見つけることはできないため、nvim-lspconfig で明示的に設定する必要があります。 ↩︎

Discussion