From 308e9719cf4b7c55c27e7bdc867e13501cc717e3 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 2 Dec 2024 12:13:23 -0600 Subject: [PATCH] fix(lsp): retrigger diagnostics request on server cancellation (#31345) (#31427) Co-authored-by: Jesse (cherry picked from commit 29c72cdf4a4913c152f037865cb28c78a8930340) --- runtime/doc/lsp.txt | 3 +- runtime/lua/vim/lsp/diagnostic.lua | 12 +++- runtime/lua/vim/lsp/handlers.lua | 3 +- runtime/lua/vim/lsp/protocol.lua | 1 + test/functional/fixtures/fake-lsp-server.lua | 15 +++++ .../functional/plugin/lsp/diagnostic_spec.lua | 62 ++++++++++++++++++- test/functional/plugin/lsp_spec.lua | 34 +++++++++- 7 files changed, 122 insertions(+), 8 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 499d28d959..5e7dfb3a03 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1472,7 +1472,7 @@ get_namespace({client_id}, {is_pull}) client. Defaults to push *vim.lsp.diagnostic.on_diagnostic()* -on_diagnostic({_}, {result}, {ctx}, {config}) +on_diagnostic({error}, {result}, {ctx}, {config}) |lsp-handler| for the method "textDocument/diagnostic" See |vim.diagnostic.config()| for configuration options. Handler-specific @@ -1497,6 +1497,7 @@ on_diagnostic({_}, {result}, {ctx}, {config}) < Parameters: ~ + • {error} (`lsp.ResponseError?`) • {result} (`lsp.DocumentDiagnosticReport`) • {ctx} (`lsp.HandlerContext`) • {config} (`vim.diagnostic.Opts`) Configuration table (see diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 5ed42700e3..223cbb3a09 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -315,11 +315,19 @@ end --- ) --- ``` --- ----@param _ lsp.ResponseError? +---@param error lsp.ResponseError? ---@param result lsp.DocumentDiagnosticReport ---@param ctx lsp.HandlerContext ---@param config vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|). -function M.on_diagnostic(_, result, ctx, config) +function M.on_diagnostic(error, result, ctx, config) + if error ~= nil and error.code == protocol.ErrorCodes.ServerCancelled then + if error.data == nil or error.data.retriggerRequest ~= false then + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + client.request(ctx.method, ctx.params) + end + return + end + if result == nil or result.kind == 'unchanged' then return end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 7018b9f61b..4d204dd1db 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -716,7 +716,8 @@ for k, fn in pairs(M) do }) end - if err then + -- ServerCancelled errors should be propagated to the request handler + if err and err.code ~= protocol.ErrorCodes.ServerCancelled then -- LSP spec: -- interface ResponseError: -- code: integer; diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 419c2ff644..a770f23ffe 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -166,6 +166,7 @@ local constants = { -- Defined by the protocol. RequestCancelled = -32800, ContentModified = -32801, + ServerCancelled = -32802, }, -- Describes the content type that a client supports in various diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f806869b40..cc69948cfa 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -386,6 +386,21 @@ function tests.check_forward_content_modified() } end +function tests.check_forward_server_cancelled() + skeleton { + on_init = function() + return { capabilities = {} } + end, + body = function() + expect_request('error_code_test', function() + return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 } + end) + expect_notification('finish') + notify('finish') + end, + } +end + function tests.check_pending_request_tracked() skeleton { on_init = function(_) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 779c4641b9..60fb24d8d7 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -264,11 +264,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua([[ + _G.requests = 0 server = _create_server({ capabilities = { - diagnosticProvider = { - } - } + diagnosticProvider = {}, + }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function get_extmarks(bufnr, client_id) @@ -409,5 +414,56 @@ describe('vim.lsp.diagnostic', function() diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) eq(1, #diags) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua([[ + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + ]]) + ) + + eq( + 2, + exec_lua([[ + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + ]]) + ) + + eq( + 2, + exec_lua([[ + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + ]]) + ) + end) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0b6e8441de..1e753a723e 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -945,6 +945,39 @@ describe('LSP', function() } end) + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client.request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client.notify('finish') + end + if ctx.method == 'finish' then + client.stop() + end + end, + } + end) + it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, @@ -964,7 +997,6 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then client.notify('finish') end