fix(lsp): send didOpen on save to all clients+groups #37454

Problem: _get_and_set_name edits the name for the whole group,
thus only one client per group gets the didOpen message.

Solution: move the logic to _changetracking and loop over every
client per group.

(cherry picked from commit 37eb1b9979)
This commit is contained in:
Emilv2
2026-04-12 16:56:12 +02:00
committed by github-actions[bot]
parent 3d1c0c9902
commit fe09c71c34
3 changed files with 78 additions and 45 deletions

View File

@@ -883,42 +883,7 @@ end
---Buffer lifecycle handler for textDocument/didSave
--- @param bufnr integer
local function text_document_did_save_handler(bufnr)
bufnr = vim._resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr)
local text = vim.func._memoize('concat', lsp._buf_get_full_text)
for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
local name = api.nvim_buf_get_name(bufnr)
local old_name = changetracking._get_and_set_name(client, bufnr, name)
if old_name and name ~= old_name then
client:notify('textDocument/didClose', {
textDocument = {
uri = vim.uri_from_fname(old_name),
},
})
client:notify('textDocument/didOpen', {
textDocument = {
version = 0,
uri = uri,
languageId = client.get_language_id(bufnr, vim.bo[bufnr].filetype),
text = lsp._buf_get_full_text(bufnr),
},
})
util.buf_versions[bufnr] = 0
end
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
if save_capability then
local included_text --- @type string?
if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
client:notify('textDocument/didSave', {
textDocument = {
uri = uri,
},
text = included_text,
})
end
end
changetracking._send_did_save(bufnr)
end
--- @type table<integer,true>

View File

@@ -165,16 +165,56 @@ function M.init(client, bufnr)
end
end
--- @param client vim.lsp.Client
--- @param bufnr integer
--- @param name string
--- @return string
function M._get_and_set_name(client, bufnr, name)
local state = state_by_group[get_group(client)] or {}
local buf_state = (state.buffers or {})[bufnr]
local old_name = buf_state.name
buf_state.name = name
return old_name
function M._send_did_save(bufnr)
local groups = {} ---@type table<string,vim.lsp.CTGroup>
for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
local group = get_group(client)
groups[group_key(group)] = group
end
local uri = vim.uri_from_bufnr(bufnr)
local text = vim.func._memoize('concat', vim.lsp._buf_get_full_text)
for _, group in pairs(groups) do
local name = api.nvim_buf_get_name(bufnr)
local state = state_by_group[group]
local buf_state = state.buffers[bufnr] or {}
local old_name = buf_state.name
buf_state.name = name
for _, client in pairs(state.clients) do
if old_name and name ~= old_name then
client:notify('textDocument/didClose', {
textDocument = {
uri = vim.uri_from_fname(old_name),
},
})
client:notify('textDocument/didOpen', {
textDocument = {
version = 0,
uri = uri,
languageId = client.get_language_id(bufnr, vim.bo[bufnr].filetype),
text = vim.lsp._buf_get_full_text(bufnr),
},
})
util.buf_versions[bufnr] = 0
end
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
if save_capability then
local included_text --- @type string?
if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
client:notify('textDocument/didSave', {
textDocument = {
uri = uri,
},
text = included_text,
})
end
end
end
end
---@param buf_state vim.lsp.CTBufferState

View File

@@ -815,6 +815,34 @@ describe('LSP', function()
}
end)
it('saveas sends didOpen to multiple attached servers if filename changed', function()
local tmpfile_new = tmpname(false)
exec_lua(create_server_definition)
local messages = exec_lua(function()
local server1 = _G._create_server()
local server2 = _G._create_server()
local client1_id = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd }))
local client2_id = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }))
vim.cmd('saveas ' .. tmpfile_new)
vim.lsp.get_client_by_id(client1_id):stop()
vim.lsp.get_client_by_id(client2_id):stop()
return {
server1 = server1.messages,
server2 = server2.messages,
}
end)
eq('textDocument/didClose', messages.server1[3].method)
eq('textDocument/didOpen', messages.server1[4].method)
eq('textDocument/didSave', messages.server1[5].method)
eq('textDocument/didClose', messages.server2[3].method)
eq('textDocument/didOpen', messages.server2[4].method)
eq('textDocument/didSave', messages.server2[5].method)
end)
it('BufWritePre does not send notifications if server lacks willSave capabilities', function()
exec_lua(create_server_definition)
local messages = exec_lua(function()