fix(lsp): skip codelens refresh redraw for deleted buffer #39193

Problem:
After on_refresh() sends a textDocument/codeLens request, the buffer may
be deleted before the response arrives. The response callback then tries
to redraw that deleted buffer and raises Invalid buffer id error.

Solution:
Check buffer validity before redrawing.

AI-assisted: Codex
Co-authored-by: Yi Ming <ofseed@foxmail.com>
(cherry picked from commit 97caa88972)
This commit is contained in:
Jaehwang Jung
2026-04-19 04:38:09 +09:00
committed by github-actions[bot]
parent 34cbfeca9c
commit 5907307662
2 changed files with 83 additions and 2 deletions
+4 -2
View File
@@ -487,8 +487,10 @@ function M.on_refresh(err, _, ctx)
-- Do nothing if a request is already scheduled.
if not provider.timer then
provider:request(client_id, function()
provider.row_version = {}
vim.api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
if api.nvim_buf_is_valid(bufnr) then
provider.row_version = {}
vim.api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
end
end)
end
end
@@ -383,6 +383,85 @@ describe('vim.lsp.codelens', function()
eq('', api.nvim_get_vvar('errmsg'))
end)
it('ignores refresh responses for deleted buffer', function()
clear_notrace()
exec_lua(create_server_definition)
insert('line1\n')
exec_lua(function()
local codelens_request_count = 0
_G.refresh_response_sent = false
_G.server = _G._create_server({
capabilities = {
codeLensProvider = {
resolveProvider = true,
},
},
handlers = {
['textDocument/codeLens'] = function(_, _, callback)
codelens_request_count = codelens_request_count + 1
local lenses = {
{
command = {
arguments = {},
command = 'dummy.command',
title = 'Lens',
},
range = {
['end'] = {
character = 1,
line = 0,
},
start = {
character = 0,
line = 0,
},
},
},
}
if codelens_request_count == 1 then
callback(nil, lenses)
else
-- Delay the refresh response so the buffer is wiped before it arrives.
vim.schedule(function()
_G.refresh_response_sent = true
callback(nil, lenses)
end)
end
end,
},
})
local client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
vim.lsp.codelens.enable()
assert(
vim.wait(1000, function()
return #vim.lsp.codelens.get() > 0
end),
'timed out waiting for initial codelens response'
)
vim.lsp.codelens.on_refresh(nil, nil, {
method = 'workspace/codeLens/refresh',
client_id = client_id,
})
vim.cmd.bwipeout({ bang = true })
assert(
vim.wait(1000, function()
return _G.refresh_response_sent
end),
'timed out waiting for refresh response'
)
end)
eq('', api.nvim_get_vvar('errmsg'))
end)
it('clears extmarks beyond the bottom of the buffer', function()
feed('12G4dd')
screen:expect([[