mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 11:15:14 -07:00
feat(lsp): add LspAttach and LspDetach autocommands
The current approach of using `on_attach` callbacks for configuring buffers for LSP is suboptimal: 1. It does not use the standard Nvim interface for driving and hooking into events (i.e. autocommands) 2. There is no way for "third parties" (e.g. plugins) to hook into the event. This means that *all* buffer configuration must go into the user-supplied on_attach callback. This also makes it impossible for these configurations to be modular, since it all must happen in the same place. 3. There is currently no way to do something when a client detaches from a buffer (there is no `on_detach` callback). The solution is to use the traditional method of event handling in Nvim: autocommands. When a LSP client is attached to a buffer, fire a `LspAttach`. Likewise, when a client detaches from a buffer fire a `LspDetach` event. This enables plugins to easily add LSP-specific configuration to buffers as well as enabling users to make their own configurations more modular (e.g. by creating multiple LspAttach autocommands that each do something unique).
This commit is contained in:
parent
8a9ab88945
commit
2ffafc7aa9
@ -461,6 +461,39 @@ LspSignatureActiveParameter
|
||||
==============================================================================
|
||||
EVENTS *lsp-events*
|
||||
|
||||
*LspAttach*
|
||||
After an LSP client attaches to a buffer. The |autocmd-pattern| is the
|
||||
name of the buffer. When used from Lua, the client ID is passed to the
|
||||
callback in the "data" table. Example: >
|
||||
|
||||
vim.api.nvim_create_autocmd("LspAttach", {
|
||||
callback = function(args)
|
||||
local bufnr = args.buf
|
||||
local client = vim.lsp.get_client_by_id(args.data.client_id)
|
||||
if client.server_capabilities.completionProvider then
|
||||
vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"
|
||||
end
|
||||
if client.server_capabilities.definitionProvider then
|
||||
vim.bo[bufnr].tagfunc = "v:lua.vim.lsp.tagfunc"
|
||||
end
|
||||
end,
|
||||
})
|
||||
<
|
||||
*LspDetach*
|
||||
Just before an LSP client detaches from a buffer. The |autocmd-pattern| is the
|
||||
name of the buffer. When used from Lua, the client ID is passed to the
|
||||
callback in the "data" table. Example: >
|
||||
|
||||
vim.api.nvim_create_autocmd("LspDetach", {
|
||||
callback = function(args)
|
||||
local client = vim.lsp.get_client_by_id(args.data.client_id)
|
||||
-- Do something with the client
|
||||
vim.cmd("setlocal tagfunc< omnifunc<")
|
||||
end,
|
||||
})
|
||||
<
|
||||
In addition, the following |User| |autocommands| are provided:
|
||||
|
||||
LspProgressUpdate *LspProgressUpdate*
|
||||
Upon receipt of a progress notification from the server. See
|
||||
|vim.lsp.util.get_progress_messages()|.
|
||||
|
@ -1,5 +1,3 @@
|
||||
local if_nil = vim.F.if_nil
|
||||
|
||||
local default_handlers = require('vim.lsp.handlers')
|
||||
local log = require('vim.lsp.log')
|
||||
local lsp_rpc = require('vim.lsp.rpc')
|
||||
@ -8,11 +6,16 @@ local util = require('vim.lsp.util')
|
||||
local sync = require('vim.lsp.sync')
|
||||
|
||||
local vim = vim
|
||||
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option =
|
||||
vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
|
||||
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
|
||||
vim.api.nvim_err_writeln,
|
||||
vim.api.nvim_buf_get_lines,
|
||||
vim.api.nvim_command,
|
||||
vim.api.nvim_buf_get_option,
|
||||
vim.api.nvim_exec_autocmds
|
||||
local uv = vim.loop
|
||||
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
|
||||
local validate = vim.validate
|
||||
local if_nil = vim.F.if_nil
|
||||
|
||||
local lsp = {
|
||||
protocol = protocol,
|
||||
@ -867,15 +870,27 @@ function lsp.start_client(config)
|
||||
pcall(config.on_exit, code, signal, client_id)
|
||||
end
|
||||
|
||||
for bufnr, client_ids in pairs(all_buffer_active_clients) do
|
||||
if client_ids[client_id] then
|
||||
vim.schedule(function()
|
||||
nvim_exec_autocmds('LspDetach', {
|
||||
buffer = bufnr,
|
||||
modeline = false,
|
||||
data = { client_id = client_id },
|
||||
})
|
||||
|
||||
local namespace = vim.lsp.diagnostic.get_namespace(client_id)
|
||||
vim.diagnostic.reset(namespace, bufnr)
|
||||
end)
|
||||
|
||||
client_ids[client_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
active_clients[client_id] = nil
|
||||
uninitialized_clients[client_id] = nil
|
||||
|
||||
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
|
||||
changetracking.reset(client_id)
|
||||
for _, client_ids in pairs(all_buffer_active_clients) do
|
||||
client_ids[client_id] = nil
|
||||
end
|
||||
|
||||
if code ~= 0 or (signal ~= 0 and signal ~= 15) then
|
||||
local msg = string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
|
||||
vim.schedule(function()
|
||||
@ -1213,6 +1228,13 @@ function lsp.start_client(config)
|
||||
---@param bufnr (number) Buffer number
|
||||
function client._on_attach(bufnr)
|
||||
text_document_did_open_handler(bufnr, client)
|
||||
|
||||
nvim_exec_autocmds('LspAttach', {
|
||||
buffer = bufnr,
|
||||
modeline = false,
|
||||
data = { client_id = client.id },
|
||||
})
|
||||
|
||||
if config.on_attach then
|
||||
-- TODO(ashkan) handle errors.
|
||||
pcall(config.on_attach, client, bufnr)
|
||||
@ -1359,6 +1381,12 @@ function lsp.buf_detach_client(bufnr, client_id)
|
||||
return
|
||||
end
|
||||
|
||||
nvim_exec_autocmds('LspDetach', {
|
||||
buffer = bufnr,
|
||||
modeline = false,
|
||||
data = { client_id = client_id },
|
||||
})
|
||||
|
||||
changetracking.reset_buf(client, bufnr)
|
||||
|
||||
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
|
||||
|
@ -70,6 +70,8 @@ return {
|
||||
'InsertEnter', -- when entering Insert mode
|
||||
'InsertLeave', -- just after leaving Insert mode
|
||||
'InsertLeavePre', -- just before leaving Insert mode
|
||||
'LspAttach', -- after an LSP client attaches to a buffer
|
||||
'LspDetach', -- after an LSP client detaches from a buffer
|
||||
'MenuPopup', -- just before popup menu is displayed
|
||||
'ModeChanged', -- after changing the mode
|
||||
'OptionSet', -- after setting any option
|
||||
@ -133,6 +135,8 @@ return {
|
||||
nvim_specific = {
|
||||
BufModifiedSet=true,
|
||||
DiagnosticChanged=true,
|
||||
LspAttach=true,
|
||||
LspDetach=true,
|
||||
RecordingEnter=true,
|
||||
RecordingLeave=true,
|
||||
Signal=true,
|
||||
|
@ -18,6 +18,7 @@ local NIL = helpers.NIL
|
||||
local read_file = require('test.helpers').read_file
|
||||
local write_file = require('test.helpers').write_file
|
||||
local isCI = helpers.isCI
|
||||
local meths = helpers.meths
|
||||
|
||||
-- Use these to get access to a coroutine so that I can run async tests and use
|
||||
-- yield.
|
||||
@ -341,6 +342,43 @@ describe('LSP', function()
|
||||
}
|
||||
end)
|
||||
|
||||
it('should fire autocommands on attach and detach', function()
|
||||
local client
|
||||
test_rpc_server {
|
||||
test_name = "basic_init";
|
||||
on_setup = function()
|
||||
exec_lua [[
|
||||
BUFFER = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_create_autocmd('LspAttach', {
|
||||
callback = function(args)
|
||||
local client = vim.lsp.get_client_by_id(args.data.client_id)
|
||||
vim.g.lsp_attached = client.name
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd('LspDetach', {
|
||||
callback = function(args)
|
||||
local client = vim.lsp.get_client_by_id(args.data.client_id)
|
||||
vim.g.lsp_detached = client.name
|
||||
end,
|
||||
})
|
||||
]]
|
||||
end;
|
||||
on_init = function(_client)
|
||||
client = _client
|
||||
eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)"))
|
||||
client.notify('finish')
|
||||
end;
|
||||
on_handler = function(_, _, ctx)
|
||||
if ctx.method == 'finish' then
|
||||
eq('basic_init', meths.get_var('lsp_attached'))
|
||||
exec_lua("return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)")
|
||||
eq('basic_init', meths.get_var('lsp_detached'))
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
}
|
||||
end)
|
||||
|
||||
it('client should return settings via workspace/configuration handler', function()
|
||||
local expected_handlers = {
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
|
Loading…
Reference in New Issue
Block a user