commit 0f3e3c87b738f1c81fa22b6e5f2a9a847d59ebd3
parent 9e9cdcaa182be4819db5d3323fdfb81477a69c37
Author: Tristan Knight <admin@snappeh.com>
Date: Sat, 6 Dec 2025 23:55:07 +0000
feat(lsp): support dynamic registration for diagnostics (#36841)
Diffstat:
5 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -284,6 +284,7 @@ LSP
• |Client:stop()| now uses the `Client.exit_timeout` field to control the default of `force`.
• Support for `workspace/diagnostic/refresh`:
https://microsoft.github.io/language-server-protocol/specification/#diagnostic_refresh
+- Support for dynamic registration for `textDocument/diagnostic`
LUA
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
@@ -358,7 +358,7 @@ end
---@param bufnr integer buffer number
---@param client_id? integer Client ID to refresh (default: all clients)
---@param only_visible? boolean Whether to only refresh for the visible regions of the buffer (default: false)
-local function refresh(bufnr, client_id, only_visible)
+function M._refresh(bufnr, client_id, only_visible)
if
only_visible
and vim.iter(api.nvim_list_wins()):all(function(window)
@@ -407,7 +407,7 @@ function M.on_refresh(err, _, ctx)
else
for bufnr in pairs(client.attached_buffers or {}) do
if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then
- refresh(bufnr)
+ M._refresh(bufnr)
end
end
end
@@ -442,7 +442,7 @@ function M._enable(bufnr)
end
if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then
local client_id = opts.data.client_id --- @type integer?
- refresh(bufnr, client_id, true)
+ M._refresh(bufnr, client_id, true)
end
end,
group = augroup,
@@ -451,7 +451,7 @@ function M._enable(bufnr)
api.nvim_buf_attach(bufnr, false, {
on_reload = function()
if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then
- refresh(bufnr)
+ M._refresh(bufnr)
end
end,
on_detach = function()
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
@@ -158,6 +158,11 @@ RSC['client/registerCapability'] = function(_, params, ctx)
end
end
end
+ if reg.method == 'textDocument/diagnostic' then
+ for bufnr in pairs(client.attached_buffers) do
+ vim.lsp.diagnostic._refresh(bufnr, client.id)
+ end
+ end
end
return vim.NIL
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
@@ -352,7 +352,7 @@ function protocol.make_client_capabilities()
},
textDocument = {
diagnostic = {
- dynamicRegistration = false,
+ dynamicRegistration = true,
tagSupport = {
valueSet = get_value_set(constants.DiagnosticTag),
},
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -498,6 +498,42 @@ describe('vim.lsp.diagnostic', function()
)
end)
+ it('supports dynamic registration', function()
+ exec_lua(create_server_definition)
+ exec_lua(function()
+ _G.server2 = _G._create_server({
+ diagnosticProvider = {
+ documentSelector = vim.NIL,
+ },
+ handlers = {
+ ['textDocument/diagnostic'] = function(_, _, callback)
+ callback(nil, {
+ kind = 'full',
+ items = {
+ _G.make_error('Dynamic Diagnostic', 4, 4, 4, 4),
+ },
+ })
+ end,
+ },
+ })
+
+ local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }))
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ { id = 'diagnostic', method = 'textDocument/diagnostic' },
+ },
+ }, { client_id = client_id2, method = 'client/registerCapability' })
+ end)
+
+ eq(
+ 1,
+ exec_lua(function()
+ return #vim.diagnostic.get(diagnostic_bufnr)
+ end)
+ )
+ end)
+
it('requests with the `previousResultId`', function()
-- Full reports
eq(