diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index b256eab1a6..1dc8c18a85 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -678,6 +678,12 @@ function Client:request(method, params, handler, bufnr) bufnr = vim._resolve_bufnr(bufnr) local version = lsp.util.buf_versions[bufnr] log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr) + + -- Detect if request resolved synchronously (only possible with in-process servers). + local already_responded = false + local request_registered = false + + -- NOTE: rpc.request might call an in-process (Lua) server, thus may be synchronous. local success, request_id = self.rpc.request(method, params, function(err, result) handler(err, result, { method = method, @@ -688,11 +694,15 @@ function Client:request(method, params, handler, bufnr) }) end, function(request_id) -- Called when the server sends a response to the request (including cancelled acknowledgment). - self:_process_request(request_id, 'complete') + if request_registered then + self:_process_request(request_id, 'complete') + end + already_responded = true end) - if success and request_id then + if success and request_id and not already_responded then self:_process_request(request_id, 'pending', bufnr, method) + request_registered = true end return success, request_id diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 3257a7bfbb..dedf2472c3 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1252,6 +1252,67 @@ describe('LSP', function() } end) + it('request should not be pending for sync responses (in-process LS)', function() + clear() + + --- @type boolean + local pending_request = exec_lua(function() + local function server(dispatchers) + local closing = false + local srv = {} + local request_id = 0 + + function srv.request(method, _params, callback, notify_reply_callback) + if method == 'textDocument/formatting' then + callback(nil, {}) + elseif method == 'initialize' then + callback(nil, { + capabilities = { + textDocument = { + formatting = true, + }, + }, + }) + elseif method == 'shutdown' then + callback(nil, nil) + end + request_id = request_id + 1 + if notify_reply_callback then + notify_reply_callback(request_id) + end + return true, request_id + end + + function srv.notify(method) + if method == 'exit' then + dispatchers.on_exit(0, 15) + end + end + function srv.is_closing() + return closing + end + function srv.terminate() + closing = true + end + + return srv + end + + local client_id = assert(vim.lsp.start({ cmd = server })) + local client = assert(vim.lsp.get_client_by_id(client_id)) + + local ok, request_id = client:request('textDocument/formatting', {}) + assert(ok) + + local has_pending = client.requests[request_id] ~= nil + vim.lsp.stop_client(client_id) + + return has_pending + end) + + eq(false, pending_request, 'expected no pending requests') + end) + it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } },