mirror of
https://github.com/neovim/neovim.git
synced 2026-04-02 05:39:26 +00:00
perf(lsp): include previousResultId in DocumentDiagnosticParams #32887
Problem:
Users of the Roslyn (C#) LSP have encountered significant delays when
retrieving pull diagnostics in large documents while using Neovim. For
instance, diagnostics in a 2000-line .cs file can take over 20 seconds
to display after edits in Neovim, whereas in VS Code, diagnostics for
the same file are displayed almost instantly.
As [mparq noted](https://github.com/seblj/roslyn.nvim/issues/93#issuecomment-2508940330)
in https://github.com/seblj/roslyn.nvim/issues/93, VS Code leverages
additional parameters specified in the [LSP documentation for
textDocument/diagnostic](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentDiagnosticParams),
specifically:
- previousResultId
- identifier
Solution:
When requesting diagnostics, Neovim should include the
`previousResultId` and `identifier` parameters as part of the request.
These parameters enable the server to utilize caching and return
incremental results.
Support for maintaining state is already present in the
[textDocument/semanticTokens implementation](8f84167c30/runtime/lua/vim/lsp/semantic_tokens.lua (L289)).
A similar mechanism can be implemented in `textDocument/diagnostic` handler.
This commit is contained in:
@@ -133,6 +133,8 @@ LSP
|
|||||||
• |vim.lsp.ClientConfig| gained `workspace_required`.
|
• |vim.lsp.ClientConfig| gained `workspace_required`.
|
||||||
• Support for `textDocument/documentColor`: |lsp-document_color|
|
• Support for `textDocument/documentColor`: |lsp-document_color|
|
||||||
https://microsoft.github.io/language-server-protocol/specification/#textDocument_documentColor
|
https://microsoft.github.io/language-server-protocol/specification/#textDocument_documentColor
|
||||||
|
• The `textDocument/diagnostic` request now includes the previous id in its
|
||||||
|
parameters.
|
||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local protocol = require('vim.lsp.protocol')
|
local protocol = require('vim.lsp.protocol')
|
||||||
local ms = protocol.Methods
|
local ms = protocol.Methods
|
||||||
|
local util = vim.lsp.util
|
||||||
|
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
|
||||||
@@ -7,6 +8,12 @@ local M = {}
|
|||||||
|
|
||||||
local augroup = api.nvim_create_augroup('nvim.lsp.diagnostic', {})
|
local augroup = api.nvim_create_augroup('nvim.lsp.diagnostic', {})
|
||||||
|
|
||||||
|
---@class (private) vim.lsp.diagnostic.BufState
|
||||||
|
---@field enabled boolean Whether diagnostics are enabled for this buffer
|
||||||
|
---@field client_result_id table<integer, string?> Latest responded `resultId`
|
||||||
|
---@type table<integer, vim.lsp.diagnostic.BufState?>
|
||||||
|
local bufstates = {}
|
||||||
|
|
||||||
local DEFAULT_CLIENT_ID = -1
|
local DEFAULT_CLIENT_ID = -1
|
||||||
|
|
||||||
---@param severity lsp.DiagnosticSeverity
|
---@param severity lsp.DiagnosticSeverity
|
||||||
@@ -256,7 +263,12 @@ function M.on_diagnostic(error, result, ctx)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
handle_diagnostics(ctx.params.textDocument.uri, ctx.client_id, result.items, true)
|
local client_id = ctx.client_id
|
||||||
|
handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true)
|
||||||
|
|
||||||
|
local bufnr = assert(ctx.bufnr)
|
||||||
|
local bufstate = assert(bufstates[bufnr])
|
||||||
|
bufstate.client_result_id[client_id] = result.resultId
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Clear push diagnostics and diagnostic cache.
|
--- Clear push diagnostics and diagnostic cache.
|
||||||
@@ -319,11 +331,6 @@ local function clear(bufnr)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class (private) lsp.diagnostic.bufstate
|
|
||||||
---@field enabled boolean Whether inlay hints are enabled for this buffer
|
|
||||||
---@type table<integer, lsp.diagnostic.bufstate>
|
|
||||||
local bufstates = {}
|
|
||||||
|
|
||||||
--- Disable pull diagnostics for a buffer
|
--- Disable pull diagnostics for a buffer
|
||||||
--- @param bufnr integer
|
--- @param bufnr integer
|
||||||
--- @private
|
--- @private
|
||||||
@@ -336,13 +343,38 @@ local function disable(bufnr)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Refresh diagnostics, only if we have attached clients that support it
|
--- Refresh diagnostics, only if we have attached clients that support it
|
||||||
---@param bufnr (integer) buffer number
|
---@param bufnr integer buffer number
|
||||||
---@param opts? table Additional options to pass to util._refresh
|
---@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)
|
||||||
---@private
|
---@private
|
||||||
local function _refresh(bufnr, opts)
|
local function _refresh(bufnr, client_id, only_visible)
|
||||||
opts = opts or {}
|
if
|
||||||
opts['bufnr'] = bufnr
|
only_visible
|
||||||
vim.lsp.util._refresh(ms.textDocument_diagnostic, opts)
|
and vim.iter(api.nvim_list_wins()):all(function(window)
|
||||||
|
return api.nvim_win_get_buf(window) ~= bufnr
|
||||||
|
end)
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local method = ms.textDocument_diagnostic
|
||||||
|
local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method, id = client_id })
|
||||||
|
local bufstate = assert(bufstates[bufnr])
|
||||||
|
|
||||||
|
util._cancel_requests({
|
||||||
|
bufnr = bufnr,
|
||||||
|
clients = clients,
|
||||||
|
method = method,
|
||||||
|
type = 'pending',
|
||||||
|
})
|
||||||
|
for _, client in ipairs(clients) do
|
||||||
|
---@type lsp.DocumentDiagnosticParams
|
||||||
|
local params = {
|
||||||
|
textDocument = util.make_text_document_params(bufnr),
|
||||||
|
previousResultId = bufstate.client_result_id[client.id],
|
||||||
|
}
|
||||||
|
client:request(method, params, nil, bufnr)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Enable pull diagnostics for a buffer
|
--- Enable pull diagnostics for a buffer
|
||||||
@@ -352,7 +384,7 @@ function M._enable(bufnr)
|
|||||||
bufnr = vim._resolve_bufnr(bufnr)
|
bufnr = vim._resolve_bufnr(bufnr)
|
||||||
|
|
||||||
if not bufstates[bufnr] then
|
if not bufstates[bufnr] then
|
||||||
bufstates[bufnr] = { enabled = true }
|
bufstates[bufnr] = { enabled = true, client_result_id = {} }
|
||||||
|
|
||||||
api.nvim_create_autocmd('LspNotify', {
|
api.nvim_create_autocmd('LspNotify', {
|
||||||
buffer = bufnr,
|
buffer = bufnr,
|
||||||
@@ -365,7 +397,7 @@ function M._enable(bufnr)
|
|||||||
end
|
end
|
||||||
if bufstates[bufnr] and bufstates[bufnr].enabled then
|
if bufstates[bufnr] and bufstates[bufnr].enabled then
|
||||||
local client_id = opts.data.client_id --- @type integer?
|
local client_id = opts.data.client_id --- @type integer?
|
||||||
_refresh(bufnr, { only_visible = true, client_id = client_id })
|
_refresh(bufnr, client_id, true)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
group = augroup,
|
group = augroup,
|
||||||
|
|||||||
@@ -215,7 +215,8 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
diagnosticProvider = {},
|
diagnosticProvider = {},
|
||||||
},
|
},
|
||||||
handlers = {
|
handlers = {
|
||||||
[vim.lsp.protocol.Methods.textDocument_diagnostic] = function()
|
[vim.lsp.protocol.Methods.textDocument_diagnostic] = function(_, params)
|
||||||
|
_G.params = params
|
||||||
_G.requests = _G.requests + 1
|
_G.requests = _G.requests + 1
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
@@ -275,6 +276,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
},
|
},
|
||||||
uri = fake_uri,
|
uri = fake_uri,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
return vim.diagnostic.get(diagnostic_bufnr)
|
return vim.diagnostic.get(diagnostic_bufnr)
|
||||||
@@ -300,6 +302,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
},
|
},
|
||||||
uri = fake_uri,
|
uri = fake_uri,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
}, {})
|
}, {})
|
||||||
return vim.diagnostic.get(diagnostic_bufnr)
|
return vim.diagnostic.get(diagnostic_bufnr)
|
||||||
end)
|
end)
|
||||||
@@ -320,6 +323,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
},
|
},
|
||||||
uri = fake_uri,
|
uri = fake_uri,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
}, {})
|
}, {})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -358,6 +362,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
},
|
},
|
||||||
uri = fake_uri,
|
uri = fake_uri,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
}, {})
|
}, {})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -392,6 +397,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
}, {}, {
|
}, {}, {
|
||||||
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
})
|
})
|
||||||
|
|
||||||
return _G.requests
|
return _G.requests
|
||||||
@@ -408,6 +414,7 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
}, {}, {
|
}, {}, {
|
||||||
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
})
|
})
|
||||||
|
|
||||||
return _G.requests
|
return _G.requests
|
||||||
@@ -424,11 +431,42 @@ describe('vim.lsp.diagnostic', function()
|
|||||||
}, {}, {
|
}, {}, {
|
||||||
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
||||||
client_id = client_id,
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
})
|
})
|
||||||
|
|
||||||
return _G.requests
|
return _G.requests
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('requests with the `previousResultId`', function()
|
||||||
|
eq(
|
||||||
|
'dummy_server',
|
||||||
|
exec_lua(function()
|
||||||
|
vim.lsp.diagnostic.on_diagnostic(nil, {
|
||||||
|
kind = 'full',
|
||||||
|
resultId = 'dummy_server',
|
||||||
|
items = {
|
||||||
|
_G.make_error('Pull Diagnostic', 4, 4, 4, 4),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
method = vim.lsp.protocol.Methods.textDocument_diagnostic,
|
||||||
|
params = {
|
||||||
|
textDocument = { uri = fake_uri },
|
||||||
|
},
|
||||||
|
client_id = client_id,
|
||||||
|
bufnr = diagnostic_bufnr,
|
||||||
|
})
|
||||||
|
vim.api.nvim_exec_autocmds('LspNotify', {
|
||||||
|
buffer = diagnostic_bufnr,
|
||||||
|
data = {
|
||||||
|
method = vim.lsp.protocol.Methods.textDocument_didChange,
|
||||||
|
client_id = client_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return _G.params.previousResultId
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user