diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /runtime/lua/vim/lsp/client.lua | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip |
Diffstat (limited to 'runtime/lua/vim/lsp/client.lua')
-rw-r--r-- | runtime/lua/vim/lsp/client.lua | 411 |
1 files changed, 189 insertions, 222 deletions
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 11ecb87507..253ccc48f4 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -75,17 +75,17 @@ local validate = vim.validate --- --- Map with language server specific settings. --- See the {settings} in |vim.lsp.Client|. ---- @field settings? table +--- @field settings? lsp.LSPObject --- --- 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)> --- --- Values to pass in the initialization request as `initializationOptions`. See `initialize` in --- the LSP spec. ---- @field init_options? table +--- @field init_options? lsp.LSPObject --- --- Name in log messages. --- (default: client-id) @@ -94,7 +94,8 @@ local validate = vim.validate --- Language ID as string. Defaults to the buffer filetype. --- @field get_language_id? fun(bufnr: integer, filetype: string): string --- ---- The encoding that the LSP server expects. Client does not verify this is correct. +--- Called "position encoding" in LSP spec, the encoding that the LSP server expects. +--- Client does not verify this is correct. --- @field offset_encoding? 'utf-8'|'utf-16'|'utf-32' --- --- Callback invoked when the client operation throws an error. `code` is a number describing the error. @@ -103,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) --- @@ -148,8 +149,10 @@ local validate = vim.validate --- See |vim.lsp.rpc.start()|. --- @field rpc vim.lsp.rpc.PublicClient --- ---- The encoding used for communicating with the server. You can modify this in ---- the `config`'s `on_init` method before text is sent to the server. +--- Called "position encoding" in LSP spec, +--- the encoding used for communicating with the server. +--- You can modify this in the `config`'s `on_init` method +--- before text is sent to the server. --- @field offset_encoding string --- --- The handlers used by the client as described in |lsp-handler|. @@ -161,16 +164,20 @@ local validate = vim.validate --- for an active request, or "cancel" for a cancel request. It will be --- "complete" ephemerally while executing |LspRequest| autocmds when replies --- are received from the server. ---- @field requests table<integer,{ type: string, bufnr: integer, method: string}> +--- @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 --- capabilities. --- @field server_capabilities lsp.ServerCapabilities? --- +--- Response from the server sent on `initialize` describing information about +--- the server. +--- @field server_info lsp.ServerInfo? +--- --- A ring buffer (|vim.ringbuf()|) containing progress messages --- sent by the server. --- @field progress vim.lsp.Client.Progress @@ -186,9 +193,6 @@ local validate = vim.validate --- --- @field attached_buffers table<integer,true> --- ---- Buffers that should be attached to upon initialize() ---- @field package _buffers_to_attach table<integer,true> ---- --- @field private _log_prefix string --- --- Track this so that we can escalate automatically if we've already tried a @@ -207,7 +211,7 @@ local validate = vim.validate --- Map with language server specific settings. These are returned to the --- language server if requested via `workspace/configuration`. Keys are --- case-sensitive. ---- @field settings table +--- @field settings lsp.LSPObject --- --- A table with flags for the client. The current (experimental) flags are: --- @field flags vim.lsp.Client.Flags @@ -219,70 +223,28 @@ local validate = vim.validate --- @field private registrations table<string,lsp.Registration[]> --- @field dynamic_capabilities lsp.DynamicCapabilities --- ---- Sends a request to the server. ---- This is a thin wrapper around {client.rpc.request} with some additional ---- checking. ---- If {handler} is not specified and if there's no respective global ---- handler, then an error will occur. ---- Returns: {status}, {client_id}?. {status} is a boolean indicating if ---- the notification was successful. If it is `false`, then it will always ---- be `false` (the client has shutdown). ---- If {status} is `true`, the function returns {request_id} as the second ---- result. You can use this with `client.cancel_request(request_id)` to cancel ---- the request. ---- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer? ---- ---- Sends a request to the server and synchronously waits for the response. ---- This is a wrapper around {client.request} ---- Returns: { err=err, result=result }, a dict, where `err` and `result` ---- come from the |lsp-handler|. On timeout, cancel or error, returns `(nil, ---- err)` where `err` is a string describing the failure reason. If the request ---- was unsuccessful returns `nil`. ---- @field request_sync fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict ---- ---- Sends a notification to an LSP server. ---- Returns: a boolean to indicate if the notification was successful. If ---- it is false, then it will always be false (the client has shutdown). ---- @field notify fun(method: string, params: table?): boolean ---- ---- Cancels a request with a given request id. ---- Returns: same as `notify()`. ---- @field cancel_request fun(id: integer): boolean ---- ---- Stops a client, optionally with force. ---- By default, it will just ask the server to shutdown without force. ---- If you request to stop a client which has previously been requested to ---- shutdown, it will automatically escalate and force shutdown. ---- @field stop fun(force?: boolean) ---- ---- Runs the on_attach function from the client's config if it was defined. ---- Useful for buffer-local setup. ---- @field on_attach fun(bufnr: integer) ---- --- @field private _before_init_cb? vim.lsp.client.before_init_cb --- @field private _on_attach_cbs vim.lsp.client.on_attach_cb[] --- @field private _on_init_cbs vim.lsp.client.on_init_cb[] --- @field private _on_exit_cbs vim.lsp.client.on_exit_cb[] --- @field private _on_error_cb? fun(code: integer, err: string) ---- ---- Checks if a client supports a given method. ---- Always returns true for unknown off-spec methods. ---- {opts} is a optional `{bufnr?: integer}` table. ---- Some language server capabilities can be file specific. ---- @field supports_method fun(method: string, opts?: {bufnr: integer?}): boolean ---- ---- Checks whether a client is stopped. ---- Returns: true if the client is fully stopped. ---- @field is_stopped fun(): boolean local Client = {} Client.__index = Client ---- @param cls table ---- @param meth any ---- @return function -local function method_wrapper(cls, meth) - return function(...) - return meth(cls, ...) +--- @param obj table<string,any> +--- @param cls table<string,function> +--- @param name string +local function method_wrapper(obj, cls, name) + local meth = assert(cls[name]) + obj[name] = function(...) + local arg = select(1, ...) + if arg and getmetatable(arg) == cls then + -- First argument is self, call meth directly + return meth(...) + end + vim.deprecate('client.' .. name, 'client:' .. name, '0.13') + -- First argument is not self, insert it + return meth(obj, ...) end end @@ -304,9 +266,6 @@ local valid_encodings = { ['utf8'] = 'utf-8', ['utf16'] = 'utf-16', ['utf32'] = 'utf-32', - UTF8 = 'utf-8', - UTF16 = 'utf-16', - UTF32 = 'utf-32', } --- Normalizes {encoding} to valid LSP encoding names. @@ -315,12 +274,12 @@ local valid_encodings = { local function validate_encoding(encoding) validate('encoding', encoding, 'string', true) if not encoding then - return valid_encodings.UTF16 + return valid_encodings.utf16 end return valid_encodings[encoding:lower()] or error( string.format( - "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", + "Invalid position encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding ) ) @@ -346,7 +305,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') @@ -404,31 +363,6 @@ local function get_name(id, config) return tostring(id) end ---- @param workspace_folders string|lsp.WorkspaceFolder[]? ---- @return lsp.WorkspaceFolder[]? -local function get_workspace_folders(workspace_folders) - if type(workspace_folders) == 'table' then - return workspace_folders - elseif type(workspace_folders) == 'string' then - return { - { - uri = vim.uri_from_fname(workspace_folders), - name = workspace_folders, - }, - } - end -end - ---- @generic T ---- @param x elem_or_list<T>? ---- @return T[] -local function ensure_list(x) - if type(x) == 'table' then - return x - end - return { x } -end - --- @nodoc --- @param config vim.lsp.ClientConfig --- @return vim.lsp.Client? @@ -455,13 +389,13 @@ function Client.create(config) settings = config.settings or {}, flags = config.flags or {}, get_language_id = config.get_language_id or default_get_language_id, - capabilities = config.capabilities or lsp.protocol.make_client_capabilities(), - workspace_folders = get_workspace_folders(config.workspace_folders or config.root_dir), + capabilities = config.capabilities, + workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir), root_dir = config.root_dir, _before_init_cb = config.before_init, - _on_init_cbs = ensure_list(config.on_init), - _on_exit_cbs = ensure_list(config.on_exit), - _on_attach_cbs = ensure_list(config.on_attach), + _on_init_cbs = vim._ensure_list(config.on_init), + _on_exit_cbs = vim._ensure_list(config.on_exit), + _on_attach_cbs = vim._ensure_list(config.on_attach), _on_error_cb = config.on_error, _trace = get_trace(config.trace), @@ -477,6 +411,9 @@ function Client.create(config) messages = { name = name, messages = {}, progress = {}, status = {} }, } + self.capabilities = + vim.tbl_deep_extend('force', lsp.protocol.make_client_capabilities(), self.capabilities or {}) + --- @class lsp.DynamicCapabilities --- @nodoc self.dynamic_capabilities = { @@ -499,24 +436,23 @@ function Client.create(config) end, } - self.request = method_wrapper(self, Client._request) - self.request_sync = method_wrapper(self, Client._request_sync) - self.notify = method_wrapper(self, Client._notify) - self.cancel_request = method_wrapper(self, Client._cancel_request) - self.stop = method_wrapper(self, Client._stop) - self.is_stopped = method_wrapper(self, Client._is_stopped) - self.on_attach = method_wrapper(self, Client._on_attach) - self.supports_method = method_wrapper(self, Client._supports_method) - --- @type table<string|integer, string> title of unfinished progress sequences by token self.progress.pending = {} --- @type vim.lsp.rpc.Dispatchers local dispatchers = { - notification = method_wrapper(self, Client._notification), - server_request = method_wrapper(self, Client._server_request), - on_error = method_wrapper(self, Client._on_error), - on_exit = method_wrapper(self, Client._on_exit), + notification = function(...) + return self:_notification(...) + end, + server_request = function(...) + return self:_server_request(...) + end, + on_error = function(...) + return self:_on_error(...) + end, + on_exit = function(...) + return self:_on_exit(...) + end, } -- Start the RPC client. @@ -533,6 +469,15 @@ function Client.create(config) setmetatable(self, Client) + method_wrapper(self, Client, 'request') + method_wrapper(self, Client, 'request_sync') + method_wrapper(self, Client, 'notify') + method_wrapper(self, Client, 'cancel_request') + method_wrapper(self, Client, 'stop') + method_wrapper(self, Client, 'is_stopped') + method_wrapper(self, Client, 'on_attach') + method_wrapper(self, Client, 'supports_method') + return self end @@ -615,8 +560,10 @@ function Client:initialize() self.offset_encoding = self.server_capabilities.positionEncoding end + self.server_info = result.serverInfo + if next(self.settings) then - self:_notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) + self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) end -- If server is being restarted, make sure to re-attach to any previously attached buffers. @@ -628,7 +575,7 @@ function Client:initialize() for buf in pairs(reattach_bufs) do -- The buffer may have been detached in the on_init callback. if self.attached_buffers[buf] then - self:_on_attach(buf) + self:on_attach(buf) end end @@ -645,24 +592,62 @@ end --- Returns the default handler if the user hasn't set a custom one. --- --- @param method (string) LSP method name ---- @return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers| +--- @return lsp.Handler? handler for the given method, if defined, or the default from |vim.lsp.handlers| function Client:_resolve_handler(method) return self.handlers[method] or lsp.handlers[method] end ---- Returns the buffer number for the given {bufnr}. ---- ---- @param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer ---- @return integer bufnr -local function resolve_bufnr(bufnr) - validate('bufnr', bufnr, 'number', true) - if bufnr == nil or bufnr == 0 then - return api.nvim_get_current_buf() +--- @private +--- @param id integer +--- @param req_type 'pending'|'complete'|'cancel'| +--- @param bufnr? integer (only required for req_type='pending') +--- @param method? string (only required for req_type='pending') +function Client:_process_request(id, req_type, bufnr, method) + local pending = req_type == 'pending' + + validate('id', id, 'number') + if pending then + validate('bufnr', bufnr, 'number') + validate('method', method, 'string') + end + + local cur_request = self.requests[id] + + if pending and cur_request then + log.error( + self._log_prefix, + ('Cannot create request with id %d as one already exists'):format(id) + ) + return + elseif not pending and not cur_request then + log.error( + self._log_prefix, + ('Cannot find request with id %d whilst attempting to %s'):format(id, req_type) + ) + return + end + + if cur_request then + bufnr = cur_request.bufnr + method = cur_request.method end - return bufnr + + assert(bufnr and method) + + local request = { type = req_type, bufnr = bufnr, method = method } + + -- Clear 'complete' requests + -- Note 'pending' and 'cancelled' requests are cleared when the server sends a response + -- which is processed via the notify_reply_callback argument to rpc.request. + self.requests[id] = req_type ~= 'complete' and request or nil + + api.nvim_exec_autocmds('LspRequest', { + buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil, + modeline = false, + data = { client_id = self.id, request_id = id, request = request }, + }) end ---- @private --- Sends a request to the server. --- --- This is a thin wrapper around {client.rpc.request} with some additional @@ -671,15 +656,14 @@ end --- @param method string LSP method name. --- @param params? table LSP request params. --- @param handler? lsp.Handler Response |lsp-handler| for this method. ---- @param bufnr integer Buffer handle (0 for current). ---- @return boolean status, integer? request_id {status} is a bool indicating ---- whether the request was successful. If it is `false`, then it will ---- always be `false` (the client has shutdown). If it was ---- successful, then it will return {request_id} as the ---- second result. You can use this with `client.cancel_request(request_id)` +--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current. +--- @return boolean status indicates whether the request was successful. +--- If it is `false`, then it will always be `false` (the client has shutdown). +--- @return integer? request_id Can be used with |Client:cancel_request()|. +--- `nil` is request failed. --- to cancel the-request. --- @see |vim.lsp.buf_request_all()| -function Client:_request(method, params, handler, bufnr) +function Client:request(method, params, handler, bufnr) if not handler then handler = assert( self:_resolve_handler(method), @@ -688,37 +672,24 @@ function Client:_request(method, params, handler, bufnr) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(self, bufnr) + bufnr = vim._resolve_bufnr(bufnr) local version = lsp.util.buf_versions[bufnr] - bufnr = resolve_bufnr(bufnr) log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr) local success, request_id = self.rpc.request(method, params, function(err, result) - local context = { + handler(err, result, { method = method, client_id = self.id, bufnr = bufnr, params = params, version = version, - } - handler(err, result, context) - end, function(request_id) - local request = self.requests[request_id] - request.type = 'complete' - api.nvim_exec_autocmds('LspRequest', { - buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil, - modeline = false, - data = { client_id = self.id, request_id = request_id, request = request }, }) - self.requests[request_id] = nil + end, function(request_id) + -- Called when the server sends a response to the request (including cancelled acknowledgment). + self:_process_request(request_id, 'complete') end) if success and request_id then - local request = { type = 'pending', bufnr = bufnr, method = method } - self.requests[request_id] = request - api.nvim_exec_autocmds('LspRequest', { - buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil, - modeline = false, - data = { client_id = self.id, request_id = request_id, request = request }, - }) + self:_process_request(request_id, 'pending', bufnr, method) end return success, request_id @@ -731,41 +702,39 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err --- --- @param ... string List to write to the buffer local function err_message(...) - local message = table.concat(vim.iter({ ... }):flatten():totable()) + local chunks = { { table.concat(vim.iter({ ... }):flatten():totable()) } } if vim.in_fast_event() then vim.schedule(function() - api.nvim_err_writeln(message) + api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end) else - api.nvim_err_writeln(message) + api.nvim_echo(chunks, true, { err = true }) api.nvim_command('redraw') end end ---- @private --- Sends a request to the server and synchronously waits for the response. --- ---- This is a wrapper around {client.request} +--- This is a wrapper around |Client:request()| --- ---- @param method (string) LSP method name. ---- @param params (table) LSP request params. ---- @param timeout_ms (integer|nil) Maximum time in milliseconds to wait for +--- @param method string LSP method name. +--- @param params table LSP request params. +--- @param timeout_ms integer? Maximum time in milliseconds to wait for --- a result. Defaults to 1000 ---- @param bufnr (integer) Buffer handle (0 for current). ---- @return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict, where ---- `err` and `result` come from the |lsp-handler|. ---- On timeout, cancel or error, returns `(nil, err)` where `err` is a ---- string describing the failure reason. If the request was unsuccessful ---- returns `nil`. +--- @param bufnr? integer (default: 0) Buffer handle, or 0 for current. +--- @return {err: lsp.ResponseError?, result:any}? `result` and `err` from the |lsp-handler|. +--- `nil` is the request was unsuccessful +--- @return string? err On timeout, cancel or error, where `err` is a +--- string describing the failure reason. --- @see |vim.lsp.buf_request_sync()| -function Client:_request_sync(method, params, timeout_ms, bufnr) +function Client:request_sync(method, params, timeout_ms, bufnr) local request_result = nil local function _sync_handler(err, result) request_result = { err = err, result = result } end - local success, request_id = self:_request(method, params, _sync_handler, bufnr) + local success, request_id = self:request(method, params, _sync_handler, bufnr) if not success then return nil end @@ -776,22 +745,20 @@ function Client:_request_sync(method, params, timeout_ms, bufnr) if not wait_result then if request_id then - self:_cancel_request(request_id) + self:cancel_request(request_id) end return nil, wait_result_reason[reason] end return request_result end ---- @package --- Sends a notification to an LSP server. --- --- @param method string LSP method name. ---- @param params table|nil LSP request params. ---- @return boolean status true if the notification was successful. ---- If it is false, then it will always be false ---- (the client has shutdown). -function Client:_notify(method, params) +--- @param params table? LSP request params. +--- @return boolean status indicating if the notification was successful. +--- If it is false, then the client has shutdown. +function Client:notify(method, params) if method ~= ms.textDocument_didChange then changetracking.flush(self) end @@ -814,41 +781,32 @@ function Client:_notify(method, params) return client_active end ---- @private --- Cancels a request with a given request id. --- ---- @param id (integer) id of request to cancel ---- @return boolean status true if notification was successful. false otherwise ---- @see |vim.lsp.client.notify()| -function Client:_cancel_request(id) - validate('id', id, 'number') - local request = self.requests[id] - if request and request.type == 'pending' then - request.type = 'cancel' - api.nvim_exec_autocmds('LspRequest', { - buffer = api.nvim_buf_is_valid(request.bufnr) and request.bufnr or nil, - modeline = false, - data = { client_id = self.id, request_id = id, request = request }, - }) - end +--- @param id integer id of request to cancel +--- @return boolean status indicating if the notification was successful. +--- @see |Client:notify()| +function Client:cancel_request(id) + self:_process_request(id, 'cancel') return self.rpc.notify(ms.dollar_cancelRequest, { id = id }) end ---- @private --- Stops a client, optionally with force. --- ---- By default, it will just ask the - server to shutdown without force. If +--- By default, it will just request the server to shutdown without force. If --- you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- ---- @param force boolean|nil -function Client:_stop(force) +--- @param force? boolean +function Client:stop(force) local rpc = self.rpc if rpc.is_closing() then return end + vim.lsp._watchfiles.cancel(self.id) + if force or not self.initialized or self._graceful_shutdown_failed then rpc.terminate() return @@ -863,7 +821,6 @@ function Client:_stop(force) rpc.terminate() self._graceful_shutdown_failed = true end - vim.lsp._watchfiles.cancel(self.id) end) end @@ -945,20 +902,22 @@ end --- @param bufnr? integer --- @return lsp.Registration? function Client:_get_registration(method, bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() + bufnr = vim._resolve_bufnr(bufnr) for _, reg in ipairs(self.registrations[method] or {}) do - if not reg.registerOptions or not reg.registerOptions.documentSelector then + local regoptions = reg.registerOptions --[[@as {documentSelector:lsp.TextDocumentFilter[]}]] + if not regoptions or not regoptions.documentSelector then return reg end - local documentSelector = reg.registerOptions.documentSelector + local documentSelector = regoptions.documentSelector local language = self:_get_language_id(bufnr) local uri = vim.uri_from_bufnr(bufnr) local fname = vim.uri_to_fname(uri) for _, filter in ipairs(documentSelector) do + local flang, fscheme, fpat = filter.language, filter.scheme, filter.pattern if - not (filter.language and language ~= filter.language) - and not (filter.scheme and not vim.startswith(uri, filter.scheme .. ':')) - and not (filter.pattern and not vim.glob.to_lpeg(filter.pattern):match(fname)) + not (flang and language ~= flang) + and not (fscheme and not vim.startswith(uri, fscheme .. ':')) + and not (type(fpat) == 'string' and not vim.glob.to_lpeg(fpat):match(fname)) then return reg end @@ -966,12 +925,11 @@ function Client:_get_registration(method, bufnr) end end ---- @private --- Checks whether a client is stopped. --- --- @return boolean # true if client is stopped or in the process of being --- stopped; false otherwise -function Client:_is_stopped() +function Client:is_stopped() return self.rpc.is_closing() end @@ -983,7 +941,7 @@ end --- @param handler? lsp.Handler only called if a server command function Client:exec_cmd(command, context, handler) context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]] - context.bufnr = context.bufnr or api.nvim_get_current_buf() + context.bufnr = vim._resolve_bufnr(context.bufnr) context.client_id = self.id local cmdname = command.command local fn = self.commands[cmdname] or lsp.commands[cmdname] @@ -1013,7 +971,7 @@ function Client:exec_cmd(command, context, handler) command = cmdname, arguments = command.arguments, } - self.request(ms.workspace_executeCommand, params, handler, context.bufnr) + self:request(ms.workspace_executeCommand, params, handler, context.bufnr) end --- Default handler for the 'textDocument/didOpen' LSP notification. @@ -1021,14 +979,14 @@ end --- @param bufnr integer Number of the buffer, or 0 for current function Client:_text_document_did_open_handler(bufnr) changetracking.init(self, bufnr) - if not self.supports_method(ms.textDocument_didOpen) then + if not self:supports_method(ms.textDocument_didOpen) then return end if not api.nvim_buf_is_loaded(bufnr) then return end - self.notify(ms.textDocument_didOpen, { + self:notify(ms.textDocument_didOpen, { textDocument = { version = lsp.util.buf_versions[bufnr], uri = vim.uri_from_bufnr(bufnr), @@ -1049,8 +1007,9 @@ function Client:_text_document_did_open_handler(bufnr) end --- Runs the on_attach function from the client's config if it was defined. +--- Useful for buffer-local setup. --- @param bufnr integer Buffer number -function Client:_on_attach(bufnr) +function Client:on_attach(bufnr) self:_text_document_did_open_handler(bufnr) lsp._set_defaults(self, bufnr) @@ -1085,10 +1044,18 @@ function Client:write_error(code, err) err_message(self._log_prefix, ': Error ', client_error, ': ', vim.inspect(err)) end ---- @private +--- Checks if a client supports a given method. +--- Always returns true for unknown off-spec methods. +--- +--- Note: Some language server capabilities can be file specific. --- @param method string ---- @param opts? {bufnr: integer?} -function Client:_supports_method(method, opts) +--- @param bufnr? integer +function Client:supports_method(method, bufnr) + -- Deprecated form + if type(bufnr) == 'table' then + --- @diagnostic disable-next-line:no-unknown + bufnr = bufnr.bufnr + end local required_capability = lsp._request_name_to_capability[method] -- if we don't know about the method, assume that the client supports it. if not required_capability then @@ -1101,12 +1068,12 @@ function Client:_supports_method(method, opts) local rmethod = lsp._resolve_to_request[method] if rmethod then if self:_supports_registration(rmethod) then - local reg = self:_get_registration(rmethod, opts and opts.bufnr) + local reg = self:_get_registration(rmethod, bufnr) return vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider') or false end else if self:_supports_registration(method) then - return self:_get_registration(method, opts and opts.bufnr) ~= nil + return self:_get_registration(method, bufnr) ~= nil end end return false @@ -1205,9 +1172,9 @@ function Client:_add_workspace_folder(dir) end end - local wf = assert(get_workspace_folders(dir)) + local wf = assert(lsp._get_workspace_folders(dir)) - self:_notify(ms.workspace_didChangeWorkspaceFolders, { + self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = wf, removed = {} }, }) @@ -1220,9 +1187,9 @@ end --- Remove a directory to the workspace folders. --- @param dir string? function Client:_remove_workspace_folder(dir) - local wf = assert(get_workspace_folders(dir)) + local wf = assert(lsp._get_workspace_folders(dir)) - self:_notify(ms.workspace_didChangeWorkspaceFolders, { + self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = {}, removed = wf }, }) |