mirror of
https://github.com/neovim/neovim.git
synced 2025-11-21 17:46:30 +00:00
fix(lsp): send didOpen if name changes on write (#19583)
`:saveas newName` changes the name of an existing buffer. Due to the buffer re-use it skips the lsp attach phase and immediately sends a `didSave` notification to the server. Servers get confused about this, because they expect a `didOpen` notification first. Closes https://github.com/neovim/neovim/issues/18688
This commit is contained in:
committed by
GitHub
parent
711ef4eac9
commit
e99de3f12f
@@ -371,7 +371,9 @@ do
|
||||
state_by_client[client.id] = state
|
||||
end
|
||||
if not state.buffers[bufnr] then
|
||||
local buf_state = {}
|
||||
local buf_state = {
|
||||
name = api.nvim_buf_get_name(bufnr),
|
||||
}
|
||||
state.buffers[bufnr] = buf_state
|
||||
if use_incremental_sync then
|
||||
buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true)
|
||||
@@ -381,6 +383,15 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
function changetracking._get_and_set_name(client, bufnr, name)
|
||||
local state = state_by_client[client.id] or {}
|
||||
local buf_state = (state.buffers or {})[bufnr]
|
||||
local old_name = buf_state.name
|
||||
buf_state.name = name
|
||||
return old_name
|
||||
end
|
||||
|
||||
---@private
|
||||
function changetracking.reset_buf(client, bufnr)
|
||||
changetracking.flush(client, bufnr)
|
||||
@@ -1405,6 +1416,19 @@ local function text_document_did_save_handler(bufnr)
|
||||
local uri = vim.uri_from_bufnr(bufnr)
|
||||
local text = once(buf_get_full_text)
|
||||
for_each_buffer_client(bufnr, function(client)
|
||||
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/didOpen', {
|
||||
textDocument = {
|
||||
version = 0,
|
||||
uri = uri,
|
||||
languageId = client.config.get_language_id(bufnr, vim.bo[bufnr].filetype),
|
||||
text = 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
|
||||
|
||||
@@ -67,10 +67,12 @@ local function expect_notification(method, params, ...)
|
||||
local message = read_message()
|
||||
assert_eq(method, message.method,
|
||||
..., "expect_notification", "method")
|
||||
if params then
|
||||
assert_eq(params, message.params,
|
||||
..., "expect_notification", method, "params")
|
||||
assert_eq({jsonrpc = "2.0"; method=method, params=params}, message,
|
||||
..., "expect_notification", "message")
|
||||
end
|
||||
end
|
||||
|
||||
local function expect_request(method, handler, ...)
|
||||
@@ -257,6 +259,26 @@ function tests.basic_check_capabilities()
|
||||
}
|
||||
end
|
||||
|
||||
function tests.text_document_save_did_open()
|
||||
skeleton {
|
||||
on_init = function()
|
||||
return {
|
||||
capabilities = {
|
||||
textDocumentSync = {
|
||||
save = true
|
||||
}
|
||||
}
|
||||
}
|
||||
end;
|
||||
body = function()
|
||||
notify('start')
|
||||
expect_notification('textDocument/didOpen')
|
||||
expect_notification('textDocument/didSave')
|
||||
notify('shutdown')
|
||||
end;
|
||||
}
|
||||
end
|
||||
|
||||
function tests.text_document_sync_save_bool()
|
||||
skeleton {
|
||||
on_init = function()
|
||||
|
||||
@@ -535,6 +535,46 @@ describe('LSP', function()
|
||||
}
|
||||
end)
|
||||
|
||||
it('saveas sends didOpen if filename changed', function()
|
||||
local expected_handlers = {
|
||||
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
|
||||
{ NIL, {}, { method = 'start', client_id = 1 } },
|
||||
}
|
||||
local client
|
||||
test_rpc_server({
|
||||
test_name = 'text_document_save_did_open',
|
||||
on_init = function(c)
|
||||
client = c
|
||||
end,
|
||||
on_exit = function(code, signal)
|
||||
eq(0, code, 'exit code')
|
||||
eq(0, signal, 'exit signal')
|
||||
end,
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler')
|
||||
if ctx.method == 'start' then
|
||||
local tmpfile_old = helpers.tmpname()
|
||||
local tmpfile_new = helpers.tmpname()
|
||||
os.remove(tmpfile_new)
|
||||
exec_lua(
|
||||
[=[
|
||||
local oldname, newname = ...
|
||||
BUFFER = vim.api.nvim_get_current_buf()
|
||||
vim.api.nvim_buf_set_name(BUFFER, oldname)
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 0, -1, true, {"help me"})
|
||||
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
|
||||
vim.api.nvim_buf_call(BUFFER, function() vim.cmd('saveas ' .. newname) end)
|
||||
]=],
|
||||
tmpfile_old,
|
||||
tmpfile_new
|
||||
)
|
||||
else
|
||||
client.stop()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
it('BufWritePost sends didSave including text if server capability is set', function()
|
||||
local expected_handlers = {
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
|
||||
Reference in New Issue
Block a user