mirror of
https://github.com/neovim/neovim.git
synced 2026-06-30 19:57:54 +00:00
fix(lsp): make LspNotify more robust #40332
Problem: LspNotify never passed a buffer when executing the autocmds, so buffer-local LspNotify autocmd subscriptions didn't have the correct buf in the event metadata. It was also wrapped in a schedule() so the actual autocmd was delayed until after the event loop. This could result in the wrong buffer receiving the notification if multiple LspNotify autocmds with buffer filters were added. Only the "latest" one would actually receive non-buffer-filtered autocmds, not the matching one. It also caused listeners to receive the notification "out of sync" with when the notification is actually sent. If a buffer is being deleted (which fires a textDocument/didClose notification), the notification is scheduled and fired after the buffer is already gone. Solution: For LSP notifications that pertain to a particular buffer, set it when executing the LspNotify autocmds so the callback functions that are filtered on that buffer will get the correct notifications and the metadata buf field will be correct. Additionally, there is no need to wrap the LspNotify callback in vim.schedule when it can be called inline when the notification to the rpc server is fired. This is tested by removing now-unnecessary autocmds from semantic tokens (InsertEnter and BufWinEnter should no longer be necessary now that requests are fired by LspNotify). Without this fix, simply modifying a buffer doesn't actually trigger LspNotify correctly, and the test for that fails.
This commit is contained in:
@@ -1780,7 +1780,7 @@ Lua module: vim.lsp.client *lsp-client*
|
||||
• {is_stopped} (`fun(self: vim.lsp.Client): boolean`) See
|
||||
|Client:is_stopped()|.
|
||||
• {name} (`string`) See |vim.lsp.ClientConfig|.
|
||||
• {notify} (`fun(self: vim.lsp.Client, method: string, params: table?): boolean`)
|
||||
• {notify} (`fun(self: vim.lsp.Client, method: string, params: table?, bufnr: integer?): boolean`)
|
||||
See |Client:notify()|.
|
||||
• {offset_encoding} (`'utf-8'|'utf-16'|'utf-32'`) See
|
||||
|vim.lsp.ClientConfig|.
|
||||
@@ -2015,12 +2015,13 @@ Client:is_stopped() *Client:is_stopped()*
|
||||
(`boolean`) true if client is stopped or in the process of being
|
||||
stopped; false otherwise
|
||||
|
||||
Client:notify({method}, {params}) *Client:notify()*
|
||||
Client:notify({method}, {params}, {bufnr}) *Client:notify()*
|
||||
Sends a notification to an LSP server.
|
||||
|
||||
Parameters: ~
|
||||
• {method} (`string`) LSP method name.
|
||||
• {params} (`table?`) LSP request params.
|
||||
• {bufnr} (`integer?`) Buffer associated with notification.
|
||||
|
||||
Return: ~
|
||||
(`boolean`) status indicating if the notification was successful. If
|
||||
|
||||
@@ -919,7 +919,7 @@ local function buf_attach(bufnr)
|
||||
reason = protocol.TextDocumentSaveReason.Manual, ---@type integer
|
||||
}
|
||||
if client:supports_method('textDocument/willSave') then
|
||||
client:notify('textDocument/willSave', params)
|
||||
client:notify('textDocument/willSave', params, bufnr)
|
||||
end
|
||||
if client:supports_method('textDocument/willSaveWaitUntil') then
|
||||
local result, err =
|
||||
@@ -955,7 +955,7 @@ local function buf_attach(bufnr)
|
||||
for _, client in ipairs(clients) do
|
||||
changetracking.reset_buf(client, bufnr)
|
||||
if client:supports_method('textDocument/didClose') then
|
||||
client:notify('textDocument/didClose', params)
|
||||
client:notify('textDocument/didClose', params, bufnr)
|
||||
end
|
||||
end
|
||||
for _, client in ipairs(clients) do
|
||||
|
||||
@@ -193,7 +193,7 @@ function M._send_did_save(bufnr)
|
||||
textDocument = {
|
||||
uri = vim.uri_from_fname(old_name),
|
||||
},
|
||||
})
|
||||
}, bufnr)
|
||||
client:notify('textDocument/didOpen', {
|
||||
textDocument = {
|
||||
version = 0,
|
||||
@@ -201,7 +201,7 @@ function M._send_did_save(bufnr)
|
||||
languageId = client.get_language_id(bufnr, vim.bo[bufnr].filetype),
|
||||
text = vim.lsp._buf_get_full_text(bufnr),
|
||||
},
|
||||
})
|
||||
}, bufnr)
|
||||
util.buf_versions[bufnr] = 0
|
||||
end
|
||||
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
|
||||
@@ -215,7 +215,7 @@ function M._send_did_save(bufnr)
|
||||
uri = uri,
|
||||
},
|
||||
text = included_text,
|
||||
})
|
||||
}, bufnr)
|
||||
else
|
||||
M.flush(client, bufnr)
|
||||
end
|
||||
@@ -329,7 +329,7 @@ local function send_changes(bufnr, sync_kind, state, buf_state)
|
||||
version = util.buf_versions[bufnr],
|
||||
},
|
||||
contentChanges = changes,
|
||||
})
|
||||
}, bufnr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -847,9 +847,10 @@ end
|
||||
---
|
||||
--- @param method vim.lsp.protocol.Method.ClientToServer.Notification LSP method name.
|
||||
--- @param params table? LSP request params.
|
||||
--- @param bufnr integer? Buffer associated with notification.
|
||||
--- @return boolean status indicating if the notification was successful.
|
||||
--- If it is false, then the client has shutdown.
|
||||
function Client:notify(method, params)
|
||||
function Client:notify(method, params, bufnr)
|
||||
if method ~= 'textDocument/didChange' then
|
||||
changetracking.flush(self)
|
||||
end
|
||||
@@ -857,16 +858,15 @@ function Client:notify(method, params)
|
||||
local client_active = self.rpc.notify(method, params)
|
||||
|
||||
if client_active then
|
||||
vim.schedule(function()
|
||||
api.nvim_exec_autocmds('LspNotify', {
|
||||
modeline = false,
|
||||
data = {
|
||||
client_id = self.id,
|
||||
method = method,
|
||||
params = params,
|
||||
},
|
||||
})
|
||||
end)
|
||||
api.nvim_exec_autocmds('LspNotify', {
|
||||
buf = bufnr,
|
||||
modeline = false,
|
||||
data = {
|
||||
client_id = self.id,
|
||||
method = method,
|
||||
params = params,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return client_active
|
||||
@@ -1137,14 +1137,14 @@ end
|
||||
|
||||
--- Default handler for the 'textDocument/didClose' LSP notification.
|
||||
---
|
||||
--- @param buf integer Number of the buffer, or 0 for current
|
||||
function Client:_text_document_did_close_handler(buf)
|
||||
--- @param bufnr integer Number of the buffer, or 0 for current
|
||||
function Client:_text_document_did_close_handler(bufnr)
|
||||
if not self:supports_method('textDocument/didClose') then
|
||||
return
|
||||
end
|
||||
local uri = vim.uri_from_bufnr(buf)
|
||||
local uri = vim.uri_from_bufnr(bufnr)
|
||||
local params = { textDocument = { uri = uri } }
|
||||
self:notify('textDocument/didClose', params)
|
||||
self:notify('textDocument/didClose', params, bufnr)
|
||||
end
|
||||
|
||||
--- Default handler for the 'textDocument/didOpen' LSP notification.
|
||||
@@ -1166,7 +1166,7 @@ function Client:_text_document_did_open_handler(bufnr)
|
||||
languageId = self:_get_language_id(bufnr),
|
||||
text = lsp._buf_get_full_text(bufnr),
|
||||
},
|
||||
})
|
||||
}, bufnr)
|
||||
|
||||
-- Next chance we get, we should re-do the diagnostics
|
||||
vim.schedule(function()
|
||||
|
||||
@@ -242,10 +242,6 @@ function STHighlighter:on_attach(client_id)
|
||||
end
|
||||
end)
|
||||
|
||||
nvim_on({ 'BufWinEnter', 'InsertLeave' }, self.augroup, { buf = self.bufnr }, function()
|
||||
self:send_request()
|
||||
end)
|
||||
|
||||
if state.supports_range then
|
||||
nvim_on('WinScrolled', self.augroup, { buf = self.bufnr }, function()
|
||||
self:debounce_request()
|
||||
@@ -802,7 +798,7 @@ function M.start(bufnr, client_id, opts)
|
||||
|
||||
M.enable(true, { bufnr = bufnr, client_id = client_id })
|
||||
|
||||
if opts and opts.debounce then
|
||||
if opts and opts.debounce and STHighlighter.active[bufnr] then
|
||||
local highlighter = STHighlighter.active[bufnr]
|
||||
local prev = rawget(highlighter, 'debounce')
|
||||
highlighter.debounce = prev and math.max(prev, opts.debounce) or opts.debounce
|
||||
|
||||
@@ -311,6 +311,7 @@ describe('semantic token highlighting', function()
|
||||
exec_lua(function()
|
||||
_G.server_full = _G._create_server({
|
||||
capabilities = {
|
||||
textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full,
|
||||
semanticTokensProvider = {
|
||||
full = { delta = false },
|
||||
range = true,
|
||||
|
||||
Reference in New Issue
Block a user