aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
authorYi Ming <ofseed@foxmail.com>2025-04-27 00:09:20 +0800
committerGitHub <noreply@github.com>2025-04-26 09:09:20 -0700
commitf486f1742e5403c3e0604fb2e8cf8761ffb316c3 (patch)
tree9da84f8c02645cfbbcc57514fc993aca33ee2e78 /runtime/lua/vim
parent83156974497df69a5f449c15d1fb472afdf7b6ff (diff)
downloadrneovim-f486f1742e5403c3e0604fb2e8cf8761ffb316c3.tar.gz
rneovim-f486f1742e5403c3e0604fb2e8cf8761ffb316c3.tar.bz2
rneovim-f486f1742e5403c3e0604fb2e8cf8761ffb316c3.zip
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](https://github.com/neovim/neovim/blob/8f84167c30692555d3332565605e8a625aebc43c/runtime/lua/vim/lsp/semantic_tokens.lua#L289). A similar mechanism can be implemented in `textDocument/diagnostic` handler.
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua60
1 files changed, 46 insertions, 14 deletions
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index f611a13c2f..700a3ea1c2 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1,5 +1,6 @@
local protocol = require('vim.lsp.protocol')
local ms = protocol.Methods
+local util = vim.lsp.util
local api = vim.api
@@ -7,6 +8,12 @@ local M = {}
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
---@param severity lsp.DiagnosticSeverity
@@ -256,7 +263,12 @@ function M.on_diagnostic(error, result, ctx)
return
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
--- Clear push diagnostics and diagnostic cache.
@@ -319,11 +331,6 @@ local function clear(bufnr)
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
--- @param bufnr integer
--- @private
@@ -336,13 +343,38 @@ local function disable(bufnr)
end
--- Refresh diagnostics, only if we have attached clients that support it
----@param bufnr (integer) buffer number
----@param opts? table Additional options to pass to util._refresh
+---@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)
---@private
-local function _refresh(bufnr, opts)
- opts = opts or {}
- opts['bufnr'] = bufnr
- vim.lsp.util._refresh(ms.textDocument_diagnostic, opts)
+local function _refresh(bufnr, client_id, only_visible)
+ if
+ only_visible
+ 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
--- Enable pull diagnostics for a buffer
@@ -352,7 +384,7 @@ function M._enable(bufnr)
bufnr = vim._resolve_bufnr(bufnr)
if not bufstates[bufnr] then
- bufstates[bufnr] = { enabled = true }
+ bufstates[bufnr] = { enabled = true, client_result_id = {} }
api.nvim_create_autocmd('LspNotify', {
buffer = bufnr,
@@ -365,7 +397,7 @@ function M._enable(bufnr)
end
if bufstates[bufnr] and bufstates[bufnr].enabled then
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,
group = augroup,