NeovimのLSPのFormatter設定を見直す
Neovim v0.10でLSPのcapabilitiesのDynamic registrationが可能になりました。日本語に訳すなら「動的にLSPの機能を登録できるようになった」といったところでしょうか。
「何がうれしいのか」「これを受けて筆者が変更した設定」を紹介します。
特にclient.server_capabilities
やBiomeを使っている人は読むといいかもしれません。
先にまとめ
- BiomeのFormatterもlspconfigだけでOK
- LSPの機能が使えるかの判定には
client.supports_method
- on_attachから必要に応じてLspAttachへ移行しよう
- Formatterの無効化は
vim.lsp.buf.format
でも可能
何がうれしいのか
今まではFormatterを動的に登録するLSPを、LSPとFormatterで別々に設定していました。
たとえば、Biomeは次のようにnvim-lspconfigでLSP、none-ls.nvimでFormatterを動かしていました。
local lspconfig = require("lspconfig")lspconfig.biome.setup({ -- 設定})
local null_ls = require("null-ls")local sources = { -- ほかのFormatterなど null_ls.builtins.formatting.biome,}null_ls.setup({ sources = sources,})
v0.10からはlspconfig
でまとめて動かせます。
local lspconfig = require("lspconfig")lspconfig.biome.setup({ -- 設定})
local null_ls = require("null-ls")local sources = { -- ほかのFormatterなど null_ls.builtins.formatting.biome,}null_ls.setup({ sources = sources,})
他のLSPではないFormatterなどは、引き続きnone-ls.nvimなり他のプラグインを通す必要があります。
動的に登録されたFormatterを保存時に実行
Biomeのように動的に登録されたFormatterを実行しようとしたらそのままでは動きませんでした。いったん修正前の設定を載せます。次の項目から小分けで解説します。
local on_attach = function(client, bufnr) local bufopts = { noremap = true, silent = true, buffer = bufnr } vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" vim.keymap.set("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", bufopts) -- その他キーマップ if client.server_capabilities.documentFormattingProvider then vim.api.nvim_create_autocmd({ "BufWritePre" }, { group = "my_nvim_rc", buffer = bufnr, callback = function() vim.lsp.buf.format({ timeout_ms = 2000 }) end, }) vim.api.nvim_create_user_command("Format", function() vim.lsp.buf.format({ timeout_ms = 2000 }) end, {}) endend
local lspconfig = require("lspconfig")lspconfig.biome.setup({ on_attach = on_attach, -- その他設定})
local mason_lspconfig = require("mason-lspconfig")mason_lspconfig.setup_handlers({ function(server_name) local opts = {} opts.on_attach = on_attach -- その他設定 lspconfig[server_name].setup(opts) end,})
Formatter自体は有効であることを確認
まず、Formatter自体は次のコマンドで問題なく動くことを確認できました。
:lua vim.lsp.buf.format()
server_capabilitiesを書き換える
client.server_capabilities
はLSPが初期化時に設定された機能しか含まれません(参考:Pull Request #23681)。
つまり、動的に登録した機能はclient.server_capabilities
からは参照できません。
実際に次のコマンドで確認してみます。
:lua vim.print(vim.lsp.get_clients({name="biome"})[1].server_capabilities)
結果を見ると、Formatterに関する記述がありませんね。
{ codeActionProvider = true, positionEncoding = "utf-16", textDocumentSync = { change = 2, openClose = true, save = { includeText = false }, willSave = false, willSaveWaitUntil = false }}
動的に登録された機能はclient.dynamic_capabilities
で確認できます。
:lua vim.print(vim.lsp.get_clients({name="biome"})[1].dynamic_capabilities)
結果を見るとtextDocument/formatting
がありました!
{ capabilities = { ["textDocument/formatting"] = { { id = "biome_formatting", method = "textDocument/formatting" } }, -- ...}
client.server_capabilities
とclient.dynamic_capabilities
に分かれており、判定のために両方を確認するのは少々面倒です。
これを一括で確認できるAPIclient.supports_method
が用意されています。
そのため、次のように書き換えます。
if client.server_capabilities.documentFormattingProvider then
on_attachからLspAttachへ
上記のclient.supports_method
へ書き換えても、まだ保存時にFormatterは実行されませんでした。
local on_attach = function(client, bufnr) -- アタッチ時の処理 if client.supports_method("textDocument/formatting") then -- Formatterの実行 endend
local lspconfig = require("lspconfig")local mason_lspconfig = require("mason-lspconfig")mason_lspconfig.setup_handlers({ function(server_name) local opts = {} opts.on_attach = on_attach -- その他設定 lspconfig[server_name].setup(opts) end,})
lspconfigのon_attachの実装を見ると、on_attachが実行されるのはバッファに入った後(BufEnter
)のようです。
バッファにLSPがアタッチされたタイミングで発火するイベントLspAttach
が用意されているのでこれを使って書き換えます。
vim.api.nvim_create_autocmd("LspAttach", { group = "my_nvim_rc", callback = function(ev) local bufopts = { noremap = true, silent = true, buffer = ev.buf } vim.bo[ev.buf].omnifunc = "v:lua.vim.lsp.omnifunc" vim.keymap.set("n", "gD", "<cmd>lua vim.lsp.buf.declaration()<CR>", bufopts) -- キーマップが続く…
local client = vim.lsp.get_client_by_id(ev.data.client_id) if client == nil then return end if client.supports_method("textDocument/formatting") then vim.api.nvim_create_autocmd({ "BufWritePre" }, { group = "my_nvim_rc", buffer = ev.bufnr, callback = function() my_format() -- 後述 end, }) vim.api.nvim_create_user_command("Format", function() my_format() end, {}) end end,})
今後のon_attach
はLSPごとに固有の設定をしたいときに使うことになりそうです。
ファイル保存時にFormatを無効にする
ここまではファイル保存時にLSPのFormatterを動かす方法を伝えました。しかし、LSPによってはFormatterを無効にしたくなるかもしれません。
以前はLSPごとに切り分けられるon_attach
を使って次のように書いていました。
local function on_attach_disable_format(client, buffer) client.server_capabilities.documentFormattingProvider = false client.on_attach = on_attachend
local lspconfig = require("lspconfig")local mason_lspconfig = require("mason-lspconfig")mason_lspconfig.setup_handlers({ function(server_name) local opts = {} opts.on_attach = on_attach if server_name == "server name" then opts.on_attach = on_attach_disable_format -- その他設定 lspconfig[server_name].setup(opts) end,})
これでも良かったのですが、このclient.server_capabilities
だと動的に登録されるFormatterの場合は無効にできません。いちいち動的なのか調べて対応するのは面倒です。
そこで、vim.lsp.buf.format
の引数filter
を使います。
local function my_format() vim.lsp.buf.format({ timeout_ms = 2000, filter = function(client) return client.name ~= "tsserver" and client.name ~= "foobar" end, })end
filter
の引数はLSPのクライアントvim.lsp.Client
であるため、名前でLSPを判定できます。
まとめ
最後にもう一度まとめです。
- BiomeのFormatterもlspconfigだけでOK
- LSPの機能が使えるかの判定には
client.supports_method
- on_attachから必要に応じてLspAttachへ移行しよう
- Formatterの無効化は
vim.lsp.buf.format
でも可能
今まで更新をサボってきたツケが回ってきた感がありますが、楽しかったので良しとします。