Skip to content

Commit f5771e0

Browse files
committed
Track lsp death more explicitly.
Fixes a nightly race condition on LSP detach and hopefully makes us less flaky in general.
1 parent 92d9b8f commit f5771e0

3 files changed

Lines changed: 52 additions & 53 deletions

File tree

lua/lean/infoview.lua

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,51 @@ local autoopen_fn = nil
118118

119119
local FOCUS_AUGROUP = vim.api.nvim_create_augroup('LeanInfoviewFocus', {})
120120

121-
---URIs whose language server has died (and not yet been restarted).
121+
---Track death of the Lean language server in each buffer it was attached to.
122122
---
123-
---Consulted when rendering so that asynchronous updates racing with the
124-
---server's death can't resurrect stale contents from it.
125-
---@type table<string, true>
126-
local dead_uris = {}
123+
---Sets a buffer-local flag which is consulted at render time (rather than
124+
---eagerly mutating pin contents here) so that asynchronous updates racing
125+
---with the server's death can't resurrect stale contents from it.
126+
local DEATH_AUGROUP = vim.api.nvim_create_augroup('LeanInfoviewDeath', {})
127+
128+
---The `leanls` client for an autocmd's client ID, if that's who it is.
129+
---@param args table autocmd callback args
130+
---@return vim.lsp.Client?
131+
local function leanls_from(args)
132+
local client = vim.lsp.get_client_by_id(args.data.client_id)
133+
return client and client.name == 'leanls' and client or nil
134+
end
135+
136+
vim.api.nvim_create_autocmd('LspDetach', {
137+
group = DEATH_AUGROUP,
138+
callback = function(args)
139+
if not leanls_from(args) then
140+
return
141+
end
142+
vim.b[args.buf].lean_lsp_died = true
143+
local uri = vim.uri_from_bufnr(args.buf)
144+
-- The server certainly isn't processing anything anymore.
145+
progress.proc_infos[uri] = {}
146+
-- LspDetach can fire in contexts where text changes aren't allowed
147+
-- (e.g. during `:edit`), so delay the re-render until it's safe.
148+
vim.schedule(function()
149+
infoview.__update_pin_by_uri(uri)
150+
end)
151+
end,
152+
})
153+
154+
vim.api.nvim_create_autocmd('LspAttach', {
155+
group = DEATH_AUGROUP,
156+
callback = function(args)
157+
if not leanls_from(args) then
158+
return
159+
end
160+
if vim.b[args.buf].lean_lsp_died then
161+
vim.b[args.buf].lean_lsp_died = nil
162+
infoview.__update_pin_by_uri(vim.uri_from_bufnr(args.buf))
163+
end
164+
end,
165+
})
127166

128167
---@class InfoviewViewOptions
129168
---@field use_widgets boolean use interactive widgets (true) or plain text (false)
@@ -790,7 +829,8 @@ end
790829
---@param sw std.Stopwatch
791830
---@return Element
792831
function Infoview:render_contents(params, sw)
793-
if dead_uris[params.textDocument.uri] then
832+
local bufnr = vim.uri_to_bufnr(params.textDocument.uri)
833+
if vim.b[bufnr].lean_lsp_died then
794834
return components.LSP_HAS_DIED
795835
end
796836
return self.__contents_for(params, self.view_options, sw)
@@ -941,31 +981,6 @@ function Infoview:__update()
941981
self.pin:request_update()
942982
end
943983

944-
---Directly mark that the infoview has died. What a shame.
945-
function Infoview:died()
946-
local params = self.pin.__position_params
947-
progress.proc_infos[params.textDocument.uri] = {
948-
{
949-
kind = progress.Kind.fatal_error,
950-
range = { start = params.position, ['end'] = params.position },
951-
},
952-
}
953-
-- Invalidate any in-flight (async) update so it can't land after us and
954-
-- resurrect stale contents from the now-dead server.
955-
self.pin.__tick = self.pin.__tick + 1
956-
self.pin.loading = false
957-
self.pin.__data_element = components.LSP_HAS_DIED
958-
self.pin.__element:set_children { self.pin.__data_element }
959-
-- LspDetach can fire in contexts where text changes aren't allowed
960-
-- (e.g. during `:edit`), so delay the re-render until it's safe.
961-
vim.schedule(function()
962-
self.pin:render()
963-
end)
964-
if self.window then
965-
self.window.o.winhighlight = 'NormalNC:leanInfoLSPDead'
966-
end
967-
end
968-
969984
---Either open or close a diff window for this infoview depending on whether it has a diff pin.
970985
function Infoview:__refresh_diff()
971986
if not self.window then
@@ -1940,27 +1955,6 @@ function infoview.init(bufnr)
19401955
-- in case we are re-entering a buffer, clear old autocmds first
19411956
vim.api.nvim_clear_autocmds { group = FOCUS_AUGROUP, buffer = bufnr }
19421957

1943-
vim.api.nvim_create_autocmd('LspDetach', {
1944-
callback = function(args)
1945-
dead_uris[vim.uri_from_bufnr(args.buf)] = true
1946-
local current_infoview = infoview.get_current_infoview()
1947-
if not current_infoview then
1948-
return
1949-
end
1950-
current_infoview:died()
1951-
end,
1952-
buffer = bufnr,
1953-
group = FOCUS_AUGROUP,
1954-
})
1955-
1956-
vim.api.nvim_create_autocmd('LspAttach', {
1957-
callback = function(args)
1958-
dead_uris[vim.uri_from_bufnr(args.buf)] = nil
1959-
end,
1960-
buffer = bufnr,
1961-
group = FOCUS_AUGROUP,
1962-
})
1963-
19641958
vim.api.nvim_create_autocmd('BufEnter', {
19651959
callback = infoview_bufenter,
19661960
buffer = bufnr,

spec/infoview/lsp_dead_spec.lua

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ describe('language server dead', function()
2222
end)
2323
assert.message("Couldn't kill the LSP!").is_true(succeeded)
2424

25+
wait:for_infoview_contents '🪦'
2526
assert.infoview_contents_nowait.are '🪦 The Lean language server is dead.'
2627
assert.are.same(
2728
'NormalNC:leanInfoLSPDead',
@@ -30,7 +31,7 @@ describe('language server dead', function()
3031

3132
-- and comes back alive
3233
vim.cmd.edit()
33-
wait:for_processing()
34+
wait:for_infoview_contents 'information'
3435
assert.infoview_contents.are [[
3536
▼ 1:1-1:6: information:
3637
1
@@ -56,6 +57,7 @@ describe('language server dead', function()
5657
return #vim.lsp.get_clients {} == 1
5758
end)
5859
assert.message("Couldn't kill the LSP!").is_true(succeeded)
60+
wait:for_infoview_contents '🪦'
5961
assert.infoview_contents_nowait.are '🪦 The Lean language server is dead.'
6062

6163
-- still alive for the other window
@@ -66,6 +68,7 @@ describe('language server dead', function()
6668
]]
6769

6870
vim.cmd.wincmd 'p'
71+
wait:for_infoview_contents '🪦'
6972
assert.infoview_contents_nowait.are '🪦 The Lean language server is dead.'
7073
end)
7174

@@ -85,6 +88,7 @@ describe('language server dead', function()
8588
assert.message("Couldn't kill the LSP!").is_true(succeeded)
8689

8790
infoview.open()
91+
wait:for_infoview_contents '🪦'
8892
assert.infoview_contents_nowait.are '🪦 The Lean language server is dead.'
8993
end)
9094
end)

spec/infoview/plain_contents_spec.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ describe('plain infoviews', function()
455455
return vim.tbl_isempty(vim.lsp.get_clients { bufnr = 0 })
456456
end)
457457
assert.message("Couldn't kill the LSP!").is_true(succeeded)
458+
helpers.wait:for_infoview_contents '🪦'
458459
assert.infoview_contents_nowait.are '🪦 The Lean language server is dead.'
459460
end)
460461
end)

0 commit comments

Comments
 (0)