nvim-lspの作用に少し凝ったカスタマイズを加える旅
はじめに
nvim-lspはデフォルトでかなり使いやすいですが、もう少し自分の使い方にあったカスタマズを加えたいと思うことはあります。
今回、これを設定するための方法を探してみたら、思いのほか長い旅の割に簡単な設定で片付いたので、ここに記録しておきます。
やりたかったこと
nvim-lspのデフォルトの設定では、signcolumnに表示されるエラー/警告/ヒントの位置が重なると、
エラー/警告/ヒントのどれが表示されるかは予測するのが難しくなっています。
しかし、エラー/警告/ヒントの表示順を深刻度の高い方から表示したいものです。
結論としての設定
今回のやりたかったことは、次のような設定で片が付きました。
vim.diagnostic.config({severity_sort = true})
シンプルですね。
模索の旅
この設定に辿り着くまでにヘルプを読みあさったので、その課程を記録しておきます。
signの表示方法を変える設定を探す
まずは、nvim-lspの設定でsignを変える方法を探してみます。
-
:help lsp
でLSP関連のヘルプを開く -
/sign
でsigncolumnに関する項目を探す
なにやら、:help lsp-handler-configuration
にぶち当たります。
*lsp-handler-configuration*
To configure the behavior of a builtin |lsp-handler|, the convenient method
|vim.lsp.with()| is provided for users.
To configure the behavior of |vim.lsp.diagnostic.on_publish_diagnostics()|,
consider the following example, where a new |lsp-handler| is created using
|vim.lsp.with()| that no longer generates signs for the diagnostics: >lua
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
-- Disable signs
signs = false,
}
)
要は、nvim-lspはvim.lsp.handlers
によって、LSからの情報を受け取り、ハンドリングしているようです。
signに関する情報は(signature
という紛らわしい情報がりますが)これ以外に周辺情報はないようです。
vim.lsp.with自体は、vim.lsp.diagnostic.on_publish_diagnostics
をラップして、新しいハンドラを作成する関数のようです。
--- Function to manage overriding defaults for LSP handlers.
---@param handler (lsp.Handler) See |lsp-handler|
---@param override_config (table) Table containing the keys to override behavior of the {handler}
function lsp.with(handler, override_config)
return function(err, result, ctx, config)
return handler(err, result, ctx, vim.tbl_deep_extend('force', config or {}, override_config))
end
end
vim.lsp.handlersについて調べる
次にvim.lsp.handlers
に関する情報を探してみます。
vim.lsp.handlersには、LSのメソッドに対して、どのような処理を行うか(ハンドラ)が管理されています。
各ハンドラは、:help lsp-handler
によると、次のようなシグネチャを持っています。
*lsp-handler*
LSP handlers are functions that handle |lsp-response|s to requests made by Nvim
to the server. (Notifications, as opposed to requests, are fire-and-forget:
there is no response, so they can't be handled. |lsp-notification|)
Each response handler has this signature: >
function(err, result, ctx, config)
<
Parameters: ~
- {err} (table|nil) Error info dict, or `nil` if the request
completed.
- {result} (Result | Params | nil) `result` key of the |lsp-response| or
`nil` if the request failed.
- {ctx} (table) Table of calling state associated with the
handler, with these keys:
- {method} (string) |lsp-method| name.
- {client_id} (number) |vim.lsp.Client| identifier.
- {bufnr} (Buffer) Buffer handle.
- {params} (table|nil) Request parameters table.
- {version} (number) Document version at time of
request. Handlers can compare this to the
current document version to check if the
response is "stale". See also |b:changedtick|.
- {config} (table) Handler-defined configuration table, which allows
users to customize handler behavior.
For an example, see:
|vim.lsp.diagnostic.on_publish_diagnostics()|
To configure a particular |lsp-handler|, see:
|lsp-handler-configuration|
Returns: ~
Two values `result, err` where `err` is shaped like an RPC error: >
{ code, message, data? }
< You can use |vim.lsp.rpc.rpc_response_error()| to create this object.
そして、前述の:help lsp-handler-configuration
に書かれた内容から、signを表示しているハンドラがわかります。
vim.lsp.diagnostic.on_publish_diagnostics()
ですね。
ちなみに、今回はたまたま検索で引っかかりましたが、各LSのメソッドに対するハンドラは、:help lsp-handlers
にまとめられています。
Lua module: vim.lsp.handlers *lsp-handlers*
hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()*
|lsp-handler| for the method "textDocument/hover"
(長いので以下略)
各ハンドラに、どのメソッドが対応しているか、どのような設定が可能かが記載されています。
ハンドラの設定内容について調べる
今回のハンドラに渡せるものは何かを確認してみます。
*vim.lsp.diagnostic.on_publish_diagnostics()*
on_publish_diagnostics({_}, {result}, {ctx}, {config})
|lsp-handler| for the method "textDocument/publishDiagnostics"
See |vim.diagnostic.config()| for configuration options. Handler-specific
configuration can be set using |vim.lsp.with()|: >lua
config({opts}, {namespace}) *vim.diagnostic.config()*
Configure diagnostic options globally or for a specific diagnostic
namespace.
Configuration can be specified globally, per-namespace, or ephemerally
(i.e. only for a single call to |vim.diagnostic.set()| or
|vim.diagnostic.show()|). Ephemeral configuration has highest priority,
followed by namespace configuration, and finally global configuration.
For example, if a user enables virtual text globally with >lua
vim.diagnostic.config({ virtual_text = true })
<
and a diagnostic producer sets diagnostics with >lua
vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
<
then virtual text will not be enabled for those diagnostics.
Parameters: ~
• {opts} (`vim.diagnostic.Opts?`) When omitted or `nil`, retrieve
the current configuration. Otherwise, a configuration
table (see |vim.diagnostic.Opts|).
*vim.diagnostic.Opts*
Each of the configuration options below accepts one of the following:
• `false`: Disable this feature
• `true`: Enable this feature, use default settings.
• `table`: Enable this feature with overrides. Use an empty table to use
default values.
• `function`: Function with signature (namespace, bufnr) that returns any
of the above.
Fields: ~
... 中略 ...
• {signs}? (`boolean|vim.diagnostic.Opts.Signs|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Signs`, default: `true`)
Use signs for diagnostics |diagnostic-signs|.
なかなかたらい回しになりましたが、diagnotic-signs
なる機構を使っているようです。
diagnostic-signsについて調べる
なんじゃらほい、と思いつつ、:help diagnostic-signs
を見てみます。
SIGNS *diagnostic-signs*
Signs are defined for each diagnostic severity. The default text for each sign
is the first letter of the severity name (for example, "E" for ERROR). Signs
can be customized with |vim.diagnostic.config()|. Example: >lua
-- Highlight entire line for errors
-- Highlight the line number for warnings
vim.diagnostic.config({
signs = {
text = {
[vim.diagnostic.severity.ERROR] = '',
[vim.diagnostic.severity.WARN] = '',
},
linehl = {
[vim.diagnostic.severity.ERROR] = 'ErrorMsg',
},
numhl = {
[vim.diagnostic.severity.WARN] = 'WarningMsg',
},
},
})
When the "severity_sort" option is set (see |vim.diagnostic.config()|) the
priority of each sign depends on the severity of the associated diagnostic.
Otherwise, all signs have the same priority (the value of the "priority"
option in the "signs" table of |vim.diagnostic.config()| or 10 if unset).
Neovimではlspの諸々とdiagnosticsの設定は独立しているとは聞いていましたが、ここに来て衝撃的なことが書かれています。
When the "severity_sort" option is set (see |vim.diagnostic.config()|) the
priority of each sign depends on the severity of the associated diagnostic.
Otherwise, all signs have the same priority (the value of the "priority"
option in the "signs" table of |vim.diagnostic.config()| or 10 if unset).
そう、vim.diagnostic.config()
でseverity_sort
なるFieldを設定すれば、signの表示優先度を変更できるようです。
• {severity_sort}? (`boolean|{reverse?:boolean}`, default: `false)
Sort diagnostics by severity. This affects the
order in which signs and virtual text are
displayed. When true, higher severities are
displayed before lower severities (e.g. ERROR is
displayed before WARN). Options:
• {reverse}? (boolean) Reverse sort order
結論としての設定(再掲)
今回のやりたかったことは、次のような設定で片が付きました。
vim.diagnostic.config({severity_sort = true})
Discussion