commit 02cd564896502462e01d08a2a0901213f96456e7
parent d847548a1cf18fa5d6cdad6c482dfd38fbbb9f91
Author: Maria Solano <majosolano99@gmail.com>
Date: Sat, 1 Nov 2025 19:12:29 -0700
feat(lsp): support auto-force escalation in client stop (#36378)
Diffstat:
4 files changed, 26 insertions(+), 43 deletions(-)
diff --git 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
@@ -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
@@ -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<integer,integer>
- 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
@@ -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