feat(lsp): add buf_detach_client (#16741)

This allows the user to detach an active buffer from the language
client. If no clients remain attached to a buffer, the on_lines callback
is used to cancel nvim_buf_attach.

(cherry picked from commit 6db2155032)

Co-authored-by: Michael Lingelbach <m.j.lbach@gmail.com>
This commit is contained in:
github-actions[bot] 2021-12-21 11:12:58 -08:00 committed by GitHub
parent 222d1414dc
commit ec101b9fd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 2 deletions

View File

@ -1111,9 +1111,9 @@ local text_document_did_change_handler
do
text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline)
-- Don't do anything if there are no clients attached.
-- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
return true
end
util.buf_versions[bufnr] = changedtick
local compute_change_and_notify = changetracking.prepare(bufnr, firstline, lastline, new_lastline)
@ -1219,6 +1219,50 @@ function lsp.buf_attach_client(bufnr, client_id)
return true
end
--- Detaches client from the specified buffer.
--- Note: While the server is notified that the text document (buffer)
--- was closed, it is still able to send notifications should it ignore this notification.
---
---@param bufnr number Buffer handle, or 0 for current
---@param client_id number Client id
function lsp.buf_detach_client(bufnr, client_id)
validate {
bufnr = {bufnr, 'n', true};
client_id = {client_id, 'n'};
}
bufnr = resolve_bufnr(bufnr)
local client = lsp.get_client_by_id(client_id)
if not client or not client.attached_buffers[bufnr] then
vim.notify(
string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)
)
return
end
changetracking.reset_buf(client, bufnr)
if client.resolved_capabilities.text_document_open_close then
local uri = vim.uri_from_bufnr(bufnr)
local params = { textDocument = { uri = uri; } }
client.notify('textDocument/didClose', params)
end
client.attached_buffers[bufnr] = nil
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr][client_id] = nil
if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then
all_buffer_active_clients[bufnr] = nil
end
local namespace = vim.lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
end
--- Checks if a buffer is attached for a particular client.
---
---@param bufnr (number) Buffer handle, or 0 for current

View File

@ -301,6 +301,43 @@ describe('LSP', function()
}
end)
it('should detach buffer in response to nvim_buf_detach', function()
local expected_handlers = {
{NIL, {}, {method="shutdown", client_id=1}};
{NIL, {}, {method="finish", client_id=1}};
}
local client
test_rpc_server {
test_name = "basic_finish";
on_setup = function()
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
]]
eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)"))
eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)"))
exec_lua [[
vim.api.nvim_command(BUFFER.."bwipeout")
]]
end;
on_init = function(_client)
client = _client
client.notify('finish')
end;
on_exit = function(code, signal)
eq(0, code, "exit code", fake_lsp_logfile)
eq(0, signal, "exit signal", fake_lsp_logfile)
end;
on_handler = function(err, result, ctx)
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
if ctx.method == 'finish' then
exec_lua("return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)")
eq(false, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)"))
client.stop()
end
end;
}
end)
it('client should return settings via workspace/configuration handler', function()
local expected_handlers = {
{NIL, {}, {method="shutdown", client_id=1}};