@ryoppippi

Neovim 0.8以降のビルトインLSPについて

26 Apr 2023 ・ 9 min read


Note

この​記事はVim 駅伝の​ 4/26 の​記事です。

実は​ Neovim 0.8 以降で​いろいろと​進化した​ LSP on Neovim に​ついての​記事が​なかったので、​書いてみます。

長らく​ Neovim で​ LSP を​導入するには​ nvim-lspconfig を​使うことが​推奨されてきました。 と​いうか、​nvim-lspconfig を​使う​前提の​解説が​ほとんどでした。

https://github.com/neovim/nvim-lspconfig

これを​使うと​ LSP の​設定を​簡単に​行うことができます。 例えば、​lua の​サーバーであるlua_lsを​使う​場合は​以下のように​設定します。

local lspconfig = require("lspconfig")

lspconfig.lua_ls.setup({})

この​設定を​行う​ことで、​lua の​ファイルを​起動すれば​自動的に​サーバーが​立ち​上がり、​lua ファイルの​バッファに​対して​補完や​ Diagnostic などの​処理を​行ってくれます。 また​バッファを​閉じれば​サーバーも​自動的に​閉じます。

今回は​ nvim-lspconfig ではなく、​Neovim 0.8 以降に​ LSP に​関していく​つかの​標準搭載された​機能を​紹介していきます。

Note

ちなみに​執筆時点での​ Neovim の​最新の​安定バージ​ョンは​ 0.9.0 です。

LspAttach/LspDetach

これが​ユーザーに​とっては​大きな​影響を​持つと​思います。

Neovim 0.8 より、LspAttach、​そしてLspDetachと​いうautocmdが​追加されました。

LspAttach は、​LSP Server が​開いた​ Buffer に​ Attach された​ときに​発火します。 これを​うまく​使うと、​設定ファイルを​書くのが​楽に​なります。

さて、​Neovim 0.7 以前で​ LSP Server の​ Attach 時に​何か​処理を​行いたい​場合は、​nvim-lspconfig のon_attachオプションに​関数を​渡していました。 つまり、​巨大なon_attach関数を​書いて、​それを​渡す必要が​あったのです(keymap から​外部​プラグインの​設定から)。 その​ため、​LSP Server 起動時の​設定は​ LSP の​設定と​まとめて​1箇所に​記述しなければなりませんでした。

local lspconfig = require("lspconfig")

-- キーマップを設定する
function setKeymap(client, buffer)
  vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', { silent = true, buffer = buffer })
end

-- 外部プラグインをlsp serverを連携させる
function setPlugin(client, buffer)
  require("illuminate").attach(client, buffer)
end

function on_attach(client, buffer)
  setKeymap(client, buffer)
  setPlugin(client, buffer)
end

lspconfig.lua_ls.setup({
  on_attach = on_attach
})

しかも、​このon_attach関数は、​LSP Server ごとに​設定する​必要が​ありました。 まあめんどくさいですよね。

これが、​LspAttach が​追加された​ことで、​設定ファイルを​うまく​分割する​ことができるようになりました。 試しに​書いてみましょう。

local lspconfig = require("lspconfig")

function on_attach(on_attach)
  vim.api.nvim_create_autocmd("LspAttach", {
    callback = function(args)
      local buffer = args.buf
      local client = vim.lsp.get_client_by_id(args.data.client_id)
      on_attach(client, buffer)
    end,
  })
end

-- キーマップを設定する
on_attach(function(client, buffer)
  vim.keymap.set('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', { silent = true, buffer = buffer })
end)

-- 外部プラグインをlsp serverを連携させる
on_attach(function(client, buffer)
  require("illuminate").attach(client, buffer)
end)

lspconfig.lua_ls.setup({})

上の​コードでは、on_attach関数で​ autocmd を​ラップして​使いやすくしています。 0.7 以前の​コードと​違って、​この​ラップ関数を​用いれば​ LSP Server 起動時の​処理を​設定ファイルの​どこに​書いても​良くなりました。 また、​LSP Server ごとに​設定する​必要もなくなりました。 特定の​ LSP Server に​対して​何か​特別に​処理を​行いたい​場合は、on_attach関数の​引数を​使ってclient.name == 'lua_ls'などのような​条件分岐を​使うことも​できます。

自分は​ lazy.nvim を​用いて​プラグインを​管理しています。​そして​設定ファイルは​プラグインごとに​分割しています。 その​ため、LspAttachを​用いる​ことで​綺麗に​設定ファイルを​分割する​ことができました。

https://github.com/ryoppippi/dotfiles/blob/1c1a2e4c7759fadff15612e20a6025e1db40114c/nvim/lua/plugin/vim-illuminate.lua

https://github.com/ryoppippi/dotfiles/blob/1c1a2e4c7759fadff15612e20a6025e1db40114c/nvim/lua/plugin/nvim-navic.lua

また、​LspDetach は​ LSP Server が​開いた​ Buffer から​ Detach された​ときに​発火します。 これの​うまい​使い所は​…​ 今の​ところ​思いついていません。​誰か​教えて​下さい

追記 2023/05/02

さて、​上ではLspAttachを​使ったon_attach関数を​紹介しました。 では​これまで​ LSP Server に​直接渡していたon_attachは​もう​不要なのかと​いうとそうでは​ありません。 Server に​直接指定するon_attachは​特定の​ Server で​のみ​有効に​したい​設定を​渡すのには​都合が​良いです。

た​とえば、​自分の​設定では​このon_attachを​使って、​特定の​ Server で​のみ​ファイルの​ Format を​無効に​する​設定を​渡しています。

https://github.com/ryoppippi/dotfiles/blob/3b19caa12b6911a738da4f39604f525176f35f48/nvim/lua/plugin/nvim-lspconfig/init.lua#L113-L117

vim.lsp.start/vim.lsp.buf_attach_client

Neovim 0.8 以降では、vim.lsp.start、​そしてvim.lsp.buf_attach_clientと​いう​関数が​追加されました。 この​ 2 つは

  • vim.lsp.start: LSP Server を​起動する
  • vim.lsp.buf_attach_client: LSP Server を​ Buffer に​ Attach する

実は、​現在の​ nvim-lspconfig は、​この​2つの​関数を​ラップした​ものになっています。 先ほどの​例に​あったlspconfig.lua_ls.setup({})では、​これらの​関数を​うまい​こと駆使して、​Buffer の​種類に​よって​最適な​サーバーを​選んで​起動し、​プロセスを​管理してくれます。

しかし、​中には​一部​挙動が​気に​食わない​場面も​出てくるかもしれません。 そんな​時に、vim.lsp.startを​そのまま​呼び出す​ことで、​完璧に​ LSP を​制御できるかもしれません。

以下に​ lua_ls の​設定例を​示します。

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,
})

このように、Filetypeの​ autocmd を​用いて、luaファイルが​開かれた​ときにlua_lsを​起動しています。 また、​例えばftplugin/python.luaと​いう​ファイルを​作成して、​そこに​設定を​書く​ことも​できますね。

メリット・デメリットは​以下の​通りです。

メリット

  • 挙動を​コントロールできる​こと

デメリット

  • 有効に​する​ファイルの​種類から、​root 判定の​ファイルの​指定、​さらに​そもそもの​ LSP Server の​起動コマンドの​指定までが​必要に​なる​こと

nvim-lspconfig は​この​設定が​大変な​部分を​うまく​やってくれているので、​基本的には​ nvim-lspconfig を​使うのが​良いと​思います。 ただ、​いく​つかの​ Server の​デフォルトでの​挙動の​一部が​自分に​とって​は​しっくりきていない​部分が​あるので​(tsserver と​ denols の​共存等)、​その​部分のみvim.lsp.startを​使って​書き直そうかなと​思っています。 (締め切りまでに​間に​合わなかったので、​後日追記します)

まとめ

Neovim 0.8 以降で​可能な​ LSP の​設定方​法を​紹介してみました。 参考に​なれば​幸いです。

参考文献

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi