From 02cd564896502462e01d08a2a0901213f96456e7 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Sat, 1 Nov 2025 19:12:29 -0700 Subject: [PATCH] feat(lsp): support auto-force escalation in client stop (#36378) --- runtime/doc/lsp.txt | 10 +++++++-- runtime/doc/news.txt | 2 ++ runtime/lua/vim/lsp.lua | 41 ++-------------------------------- runtime/lua/vim/lsp/client.lua | 16 +++++++++++-- 4 files changed, 26 insertions(+), 43 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 673704a7a4..2004d27652 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1667,7 +1667,7 @@ Lua module: vim.lsp.client *lsp-client* See |Client:notify()|. • {cancel_request} (`fun(self: vim.lsp.Client, id: integer): boolean`) See |Client:cancel_request()|. - • {stop} (`fun(self: vim.lsp.Client, force: boolean?)`) + • {stop} (`fun(self: vim.lsp.Client, force: boolean|integer?)`) See |Client:stop()|. • {is_stopped} (`fun(self: vim.lsp.Client): boolean`) See |Client:is_stopped()|. @@ -1915,8 +1915,14 @@ Client:stop({force}) *Client:stop()* you request to stop a client which has previously been requested to shutdown, it will automatically escalate and force shutdown. + If `force` is a number, it will be treated as the time in milliseconds to + wait before forcing the shutdown. + + Note: Forcing shutdown while a server is busy writing out project or index + files can lead to file corruption. + Parameters: ~ - • {force} (`boolean?`) + • {force} (`boolean|integer?`) Client:supports_method({method}, {bufnr}) *Client:supports_method()* Checks if a client supports a given method. Always returns true for diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 02fe076fd8..5452990b68 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -260,6 +260,8 @@ LSP • Support for `textDocument/onTypeFormatting`: |lsp-on_type_formatting| https://microsoft.github.io/language-server-protocol/specification/#textDocument_onTypeFormatting • The filter option of |vim.lsp.buf.code_action()| now receives the client ID as an argument. +• |Client:stop()| now accepts a numerical `force` argument to be interpreted as the time to wait + before forcing the shutdown. LUA diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index bb1fe6fa20..ca3d14a974 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1132,46 +1132,9 @@ api.nvim_create_autocmd('VimLeavePre', { callback = function() local active_clients = lsp.get_clients() log.info('exit_handler', active_clients) - for _, client in pairs(lsp.client._all) do - client:stop() - end - local timeouts = {} --- @type table - local max_timeout = 0 - local send_kill = false - - for client_id, client in pairs(active_clients) do - local timeout = client.flags.exit_timeout - if timeout then - send_kill = true - timeouts[client_id] = timeout - max_timeout = math.max(timeout, max_timeout) - end - end - - local poll_time = 50 - - local function check_clients_closed() - for client_id, timeout in pairs(timeouts) do - timeouts[client_id] = timeout - poll_time - end - - for client_id, _ in pairs(active_clients) do - if timeouts[client_id] ~= nil and timeouts[client_id] > 0 then - return false - end - end - return true - end - - if send_kill then - if not vim.wait(max_timeout, check_clients_closed, poll_time) then - for client_id, client in pairs(active_clients) do - if timeouts[client_id] ~= nil then - client:stop(true) - end - end - end + for _, client in pairs(active_clients) do + client:stop(client.flags.exit_timeout) end end, }) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index abf86a843a..c0f9586db2 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -865,8 +865,20 @@ end --- you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- ---- @param force? boolean +--- If `force` is a number, it will be treated as the time in milliseconds to +--- wait before forcing the shutdown. +--- +--- Note: Forcing shutdown while a server is busy writing out project or index +--- files can lead to file corruption. +--- +--- @param force? boolean|integer function Client:stop(force) + if type(force) == 'number' then + vim.defer_fn(function() + self:stop(true) + end, force) + end + local rpc = self.rpc if rpc.is_closing() then return @@ -886,7 +898,7 @@ function Client:stop(force) if err == nil then rpc.notify('exit') else - -- If there was an error in the shutdown request, then term to be safe. + -- If there was an error in the shutdown request, then terminate to be safe. rpc.terminate() self._graceful_shutdown_failed = true end