diff options
author | Maria José Solano <majosolano99@gmail.com> | 2024-12-04 05:14:47 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-04 05:14:47 -0800 |
commit | e56437cd48f7df87ccdfb79812ee56241c0da0cb (patch) | |
tree | 95ace99b60a4b237f714c17717788a1e08d18247 | |
parent | b079a9d2e76062ee7275e35a4623108550e836a5 (diff) | |
download | rneovim-e56437cd48f7df87ccdfb79812ee56241c0da0cb.tar.gz rneovim-e56437cd48f7df87ccdfb79812ee56241c0da0cb.tar.bz2 rneovim-e56437cd48f7df87ccdfb79812ee56241c0da0cb.zip |
feat(lsp): deprecate vim.lsp.start_client #31341
Problem:
LSP module has multiple "start" interfaces.
Solution:
- Enhance vim.lsp.start
- Deprecate vim.lsp.start_client
-rw-r--r-- | runtime/doc/deprecated.txt | 1 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 36 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 208 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/client.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 2 | ||||
-rw-r--r-- | test/functional/plugin/lsp/diagnostic_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/plugin/lsp/semantic_tokens_spec.lua | 2 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 4 |
8 files changed, 139 insertions, 126 deletions
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index c6ca5e5ce9..ab9c0b2ce8 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -64,6 +64,7 @@ LSP • `client.is_stopped()` Use |Client:is_stopped()| instead. • `client.supports_method()` Use |Client:supports_method()| instead. • `client.on_attach()` Use |Client:on_attach()| instead. +• `vim.lsp.start_client()` Use |vim.lsp.start()| instead. ------------------------------------------------------------------------------ DEPRECATED IN 0.10 *deprecated-0.10* diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index e311831bd3..2654d7f14f 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -841,12 +841,11 @@ start({config}, {opts}) *vim.lsp.start()* }) < - See |vim.lsp.start_client()| for all available options. The most important + See |vim.lsp.ClientConfig| for all available options. The most important are: • `name` arbitrary name for the LSP client. Should be unique per language server. - • `cmd` command string[] or function, described at - |vim.lsp.start_client()|. + • `cmd` command string[] or function. • `root_dir` path to the project root. By default this is used to decide if an existing client should be re-used. The example above uses |vim.fs.root()| to detect the root by traversing the file system upwards @@ -868,7 +867,7 @@ start({config}, {opts}) *vim.lsp.start()* Parameters: ~ • {config} (`vim.lsp.ClientConfig`) Configuration for the server. See |vim.lsp.ClientConfig|. - • {opts} (`table?`) Optional keyword arguments + • {opts} (`table?`) Optional keyword arguments. • {reuse_client}? (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`) Predicate used to decide if a client should be re-used. @@ -876,25 +875,15 @@ start({config}, {opts}) *vim.lsp.start()* re-uses a client if name and root_dir matches. • {bufnr}? (`integer`) Buffer handle to attach to if starting or re-using a client (0 for current). + • {attach}? (`boolean`) Whether to attach the client to a + buffer (default true). If set to `false`, `reuse_client` + and `bufnr` will be ignored. • {silent}? (`boolean`) Suppress error reporting if the LSP server fails to start (default false). Return: ~ (`integer?`) client_id -start_client({config}) *vim.lsp.start_client()* - Starts and initializes a client with the given configuration. - - Parameters: ~ - • {config} (`vim.lsp.ClientConfig`) Configuration for the server. See - |vim.lsp.ClientConfig|. - - Return (multiple): ~ - (`integer?`) client_id |vim.lsp.get_client_by_id()| Note: client may - not be fully initialized. Use `on_init` to do any actions once the - client has been initialized. - (`string?`) Error message, if any - status() *vim.lsp.status()* Consumes the latest progress messages from all clients and formats them as a string. Empty if there are no clients or if no new messages @@ -968,8 +957,7 @@ Lua module: vim.lsp.client *lsp-client* server. • {config} (`vim.lsp.ClientConfig`) copy of the table that was passed by the user to - |vim.lsp.start_client()|. See - |vim.lsp.ClientConfig|. + |vim.lsp.start()|. See |vim.lsp.ClientConfig|. • {server_capabilities} (`lsp.ServerCapabilities?`) Response from the server sent on `initialize` describing the server's capabilities. @@ -1093,7 +1081,7 @@ Lua module: vim.lsp.client *lsp-client* • {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`) Table that maps string of clientside commands to user-defined functions. Commands passed to - start_client take precedence over the global + `start()` take precedence over the global command registry. Each key must be a unique command name, and the value is a function which is called if any LSP action (code action, code @@ -1122,9 +1110,9 @@ Lua module: vim.lsp.client *lsp-client* Callback invoked before the LSP "initialize" phase, where `params` contains the parameters being sent to the server and `config` is the - config that was passed to - |vim.lsp.start_client()|. You can use this to - modify parameters before they are sent. + config that was passed to |vim.lsp.start()|. You + can use this to modify parameters before they + are sent. • {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)>`) Callback invoked after LSP "initialize", where `result` is a table of `capabilities` and @@ -2296,7 +2284,7 @@ connect({host_or_path}, {port}) *vim.lsp.rpc.connect()* • a host and port via TCP Return a function that can be passed to the `cmd` field for - |vim.lsp.start_client()| or |vim.lsp.start()|. + |vim.lsp.start()|. Parameters: ~ • {host_or_path} (`string`) host to connect to or path to a pipe/domain diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b1a3316e3e..4717d7995a 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -211,17 +211,117 @@ local function reuse_client_default(client, config) return false end +--- Reset defaults set by `set_defaults`. +--- Must only be called if the last client attached to a buffer exits. +local function reset_defaults(bufnr) + if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then + vim.bo[bufnr].tagfunc = nil + end + if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then + vim.bo[bufnr].omnifunc = nil + end + if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then + vim.bo[bufnr].formatexpr = nil + end + vim._with({ buf = bufnr }, function() + local keymap = vim.fn.maparg('K', 'n', false, true) + if keymap and keymap.callback == vim.lsp.buf.hover and keymap.buffer == 1 then + vim.keymap.del('n', 'K', { buffer = bufnr }) + end + end) +end + +--- @param code integer +--- @param signal integer +--- @param client_id integer +local function on_client_exit(code, signal, client_id) + local client = all_clients[client_id] + + vim.schedule(function() + for bufnr in pairs(client.attached_buffers) do + if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id }, + }) + end + + client.attached_buffers[bufnr] = nil + + if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then + reset_defaults(bufnr) + end + end + + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace) + end) + + local name = client.name or 'unknown' + + -- Schedule the deletion of the client object so that it exists in the execution of LspDetach + -- autocommands + vim.schedule(function() + all_clients[client_id] = nil + + -- Client can be absent if executable starts, but initialize fails + -- init/attach won't have happened + if client then + changetracking.reset(client) + 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. Check log for errors: %s', + name, + code, + signal, + lsp.get_log_path() + ) + vim.notify(msg, vim.log.levels.WARN) + end + end) +end + +--- Creates and initializes a client with the given configuration. +--- @param config vim.lsp.ClientConfig Configuration for the server. +--- @return integer? client_id |vim.lsp.get_client_by_id()| Note: client may not be +--- fully initialized. Use `on_init` to do any actions once +--- the client has been initialized. +--- @return string? # Error message, if any +local function create_and_initialize_client(config) + local ok, res = pcall(require('vim.lsp.client').create, config) + if not ok then + return nil, res --[[@as string]] + end + + local client = assert(res) + + --- @diagnostic disable-next-line: invisible + table.insert(client._on_exit_cbs, on_client_exit) + + all_clients[client.id] = client + + client:initialize() + + return client.id, nil +end + --- @class vim.lsp.start.Opts --- @inlinedoc --- --- Predicate used to decide if a client should be re-used. Used on all --- running clients. The default implementation re-uses a client if name and --- root_dir matches. ---- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean +--- @field reuse_client? (fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean) --- --- Buffer handle to attach to if starting or re-using a client (0 for current). --- @field bufnr? integer --- +--- Whether to attach the client to a buffer (default true). +--- If set to `false`, `reuse_client` and `bufnr` will be ignored. +--- @field attach? boolean +--- --- Suppress error reporting if the LSP server fails to start (default false). --- @field silent? boolean @@ -239,10 +339,10 @@ end --- }) --- ``` --- ---- See |vim.lsp.start_client()| for all available options. The most important are: +--- See |vim.lsp.ClientConfig| for all available options. The most important are: --- --- - `name` arbitrary name for the LSP client. Should be unique per language server. ---- - `cmd` command string[] or function, described at |vim.lsp.start_client()|. +--- - `cmd` command string[] or function. --- - `root_dir` path to the project root. By default this is used to decide if an existing client --- should be re-used. The example above uses |vim.fs.root()| to detect the root by traversing --- the file system upwards starting from the current directory until either a `pyproject.toml` @@ -262,7 +362,7 @@ end --- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|) --- --- @param config vim.lsp.ClientConfig Configuration for the server. ---- @param opts vim.lsp.start.Opts? Optional keyword arguments +--- @param opts vim.lsp.start.Opts? Optional keyword arguments. --- @return integer? client_id function lsp.start(config, opts) opts = opts or {} @@ -271,6 +371,10 @@ function lsp.start(config, opts) for _, client in pairs(all_clients) do if reuse_client(client, config) then + if opts.attach == false then + return client.id + end + if lsp.buf_attach_client(bufnr, client.id) then return client.id else @@ -279,7 +383,7 @@ function lsp.start(config, opts) end end - local client_id, err = lsp.start_client(config) + local client_id, err = create_and_initialize_client(config) if err then if not opts.silent then vim.notify(err, vim.log.levels.WARN) @@ -287,6 +391,10 @@ function lsp.start(config, opts) return nil end + if opts.attach == false then + return client_id + end + if client_id and lsp.buf_attach_client(bufnr, client_id) then return client_id end @@ -383,78 +491,7 @@ function lsp._set_defaults(client, bufnr) end end ---- Reset defaults set by `set_defaults`. ---- Must only be called if the last client attached to a buffer exits. -local function reset_defaults(bufnr) - if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then - vim.bo[bufnr].tagfunc = nil - end - if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then - vim.bo[bufnr].omnifunc = nil - end - if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then - vim.bo[bufnr].formatexpr = nil - end - vim._with({ buf = bufnr }, function() - local keymap = vim.fn.maparg('K', 'n', false, true) - if keymap and keymap.callback == vim.lsp.buf.hover and keymap.buffer == 1 then - vim.keymap.del('n', 'K', { buffer = bufnr }) - end - end) -end - ---- @param code integer ---- @param signal integer ---- @param client_id integer -local function on_client_exit(code, signal, client_id) - local client = all_clients[client_id] - - vim.schedule(function() - for bufnr in pairs(client.attached_buffers) do - if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) - end - - client.attached_buffers[bufnr] = nil - - if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then - reset_defaults(bufnr) - end - end - - local namespace = vim.lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace) - end) - - local name = client.name or 'unknown' - - -- Schedule the deletion of the client object so that it exists in the execution of LspDetach - -- autocommands - vim.schedule(function() - all_clients[client_id] = nil - - -- Client can be absent if executable starts, but initialize fails - -- init/attach won't have happened - if client then - changetracking.reset(client) - 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. Check log for errors: %s', - name, - code, - signal, - lsp.get_log_path() - ) - vim.notify(msg, vim.log.levels.WARN) - end - end) -end - +--- @deprecated --- Starts and initializes a client with the given configuration. --- @param config vim.lsp.ClientConfig Configuration for the server. --- @return integer? client_id |vim.lsp.get_client_by_id()| Note: client may not be @@ -462,21 +499,8 @@ end --- the client has been initialized. --- @return string? # Error message, if any function lsp.start_client(config) - local ok, res = pcall(require('vim.lsp.client').create, config) - if not ok then - return nil, res --[[@as string]] - end - - local client = assert(res) - - --- @diagnostic disable-next-line: invisible - table.insert(client._on_exit_cbs, on_client_exit) - - all_clients[client.id] = client - - client:initialize() - - return client.id, nil + vim.deprecate('vim.lsp.start_client()', 'vim.lsp.start()', '0.13') + return create_and_initialize_client(config) end ---Buffer lifecycle handler for textDocument/didSave diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index a14b6ccda6..a83d75bf75 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -78,7 +78,7 @@ local validate = vim.validate --- @field settings? table --- --- Table that maps string of clientside commands to user-defined functions. ---- Commands passed to start_client take precedence over the global command registry. Each key +--- Commands passed to `start()` take precedence over the global command registry. Each key --- must be a unique command name, and the value is a function which is called if any LSP action --- (code action, code lenses, ...) triggers the command. --- @field commands? table<string,fun(command: lsp.Command, ctx: table)> @@ -104,7 +104,7 @@ local validate = vim.validate --- @field on_error? fun(code: integer, err: string) --- --- Callback invoked before the LSP "initialize" phase, where `params` contains the parameters ---- being sent to the server and `config` is the config that was passed to |vim.lsp.start_client()|. +--- being sent to the server and `config` is the config that was passed to |vim.lsp.start()|. --- You can use this to modify parameters before they are sent. --- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig) --- @@ -167,7 +167,7 @@ local validate = vim.validate --- @field requests table<integer,{ type: string, bufnr: integer, method: string}> --- --- copy of the table that was passed by the user ---- to |vim.lsp.start_client()|. +--- to |vim.lsp.start()|. --- @field config vim.lsp.ClientConfig --- --- Response from the server sent on `initialize` describing the server's @@ -307,7 +307,7 @@ local function default_get_language_id(_bufnr, filetype) return filetype end ---- Validates a client configuration as given to |vim.lsp.start_client()|. +--- Validates a client configuration as given to |vim.lsp.start()|. --- @param config vim.lsp.ClientConfig local function validate_config(config) validate('config', config, 'table') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 6c8564845f..2327a37ab1 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -617,7 +617,7 @@ end --- - a host and port via TCP --- --- Return a function that can be passed to the `cmd` field for ---- |vim.lsp.start_client()| or |vim.lsp.start()|. +--- |vim.lsp.start()|. --- ---@param host_or_path string host to connect to or path to a pipe/domain socket ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index ca9196562c..4ecb056d01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function() return extmarks end - client_id = assert(vim.lsp.start_client { + client_id = assert(vim.lsp.start({ cmd_env = { NVIM_LUA_NOTRACK = '1', }, @@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function() '--headless', }, offset_encoding = 'utf-16', - }) + }, { attach = false })) end) fake_uri = 'file:///fake/uri' diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 280bd27207..9912bf2063 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -456,7 +456,7 @@ describe('semantic token highlighting', function() vim.notify = function(...) table.insert(_G.notifications, 1, { ... }) end - return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false }) end) eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index e30d1ba411..e735e20ff5 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -95,7 +95,7 @@ describe('LSP', function() exec_lua(function() _G.lsp = require('vim.lsp') function _G.test__start_client() - return vim.lsp.start_client { + return vim.lsp.start({ cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile, NVIM_APPNAME = 'nvim_lsp_test', @@ -112,7 +112,7 @@ describe('LSP', function() name = 'test_folder', }, }, - } + }, { attach = false }) end _G.TEST_CLIENT1 = _G.test__start_client() end) |