mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	 165b099fa3
			
		
	
	165b099fa3
	
	
	
		
			
			Problem: LSP spec uses the term "position encoding" where we say "offset encoding". Solution: - Rename it everywhere except `vim.lsp.Client.offset_encoding` (which would be breaking). - Mention "position encoding" in the documentation for `vim.lsp.Client.offset_encoding`.
		
			
				
	
	
		
			1207 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1207 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local uv = vim.uv
 | |
| local api = vim.api
 | |
| local lsp = vim.lsp
 | |
| local log = lsp.log
 | |
| local ms = lsp.protocol.Methods
 | |
| local changetracking = lsp._changetracking
 | |
| local validate = vim.validate
 | |
| 
 | |
| --- @alias vim.lsp.client.on_init_cb fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)
 | |
| --- @alias vim.lsp.client.on_attach_cb fun(client: vim.lsp.Client, bufnr: integer)
 | |
| --- @alias vim.lsp.client.on_exit_cb fun(code: integer, signal: integer, client_id: integer)
 | |
| --- @alias vim.lsp.client.before_init_cb fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
 | |
| 
 | |
| --- @class vim.lsp.Client.Flags
 | |
| --- @inlinedoc
 | |
| ---
 | |
| --- Allow using incremental sync for buffer edits
 | |
| --- (default: `true`)
 | |
| --- @field allow_incremental_sync? boolean
 | |
| ---
 | |
| --- Debounce `didChange` notifications to the server by the given number in milliseconds.
 | |
| --- No debounce occurs if `nil`.
 | |
| --- (default: `150`)
 | |
| --- @field debounce_text_changes integer
 | |
| ---
 | |
| --- Milliseconds to wait for server to exit cleanly after sending the
 | |
| --- "shutdown" request before sending kill -15. If set to false, nvim exits
 | |
| --- immediately after sending the "shutdown" request to the server.
 | |
| --- (default: `false`)
 | |
| --- @field exit_timeout integer|false
 | |
| 
 | |
| --- @class vim.lsp.ClientConfig
 | |
| --- command string[] that launches the language
 | |
| --- server (treated as in |jobstart()|, must be absolute or on `$PATH`, shell constructs like
 | |
| --- "~" are not expanded), or function that creates an RPC client. Function receives
 | |
| --- a `dispatchers` table and returns a table with member functions `request`, `notify`,
 | |
| --- `is_closing` and `terminate`.
 | |
| --- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|.
 | |
| ---  For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()|
 | |
| --- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
 | |
| ---
 | |
| --- Directory to launch the `cmd` process. Not related to `root_dir`.
 | |
| --- (default: cwd)
 | |
| --- @field cmd_cwd? string
 | |
| ---
 | |
| --- Environment flags to pass to the LSP on spawn.
 | |
| --- Must be specified using a table.
 | |
| --- Non-string values are coerced to string.
 | |
| --- Example:
 | |
| --- ```lua
 | |
| --- { PORT = 8080; HOST = "0.0.0.0"; }
 | |
| --- ```
 | |
| --- @field cmd_env? table
 | |
| ---
 | |
| --- Daemonize the server process so that it runs in a separate process group from Nvim.
 | |
| --- Nvim will shutdown the process on exit, but if Nvim fails to exit cleanly this could leave
 | |
| --- behind orphaned server processes.
 | |
| --- (default: true)
 | |
| --- @field detached? boolean
 | |
| ---
 | |
| --- List of workspace folders passed to the language server.
 | |
| --- For backwards compatibility rootUri and rootPath will be derived from the first workspace
 | |
| --- folder in this list. See `workspaceFolders` in the LSP spec.
 | |
| --- @field workspace_folders? lsp.WorkspaceFolder[]
 | |
| ---
 | |
| --- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
 | |
| --- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
 | |
| --- its result.
 | |
| --- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
 | |
| ---   array.
 | |
| --- @field capabilities? lsp.ClientCapabilities
 | |
| ---
 | |
| --- Map of language server method names to |lsp-handler|
 | |
| --- @field handlers? table<string,function>
 | |
| ---
 | |
| --- Map with language server specific settings.
 | |
| --- See the {settings} in |vim.lsp.Client|.
 | |
| --- @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
 | |
| --- 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
 | |
| ---
 | |
| --- Name in log messages.
 | |
| --- (default: client-id)
 | |
| --- @field name? string
 | |
| ---
 | |
| --- Language ID as string. Defaults to the buffer filetype.
 | |
| --- @field get_language_id? fun(bufnr: integer, filetype: string): string
 | |
| ---
 | |
| --- 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.
 | |
| --- Other arguments may be passed depending on the error kind.  See `vim.lsp.rpc.client_errors`
 | |
| --- for possible errors. Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
 | |
| --- @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()|.
 | |
| --- You can use this to modify parameters before they are sent.
 | |
| --- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
 | |
| ---
 | |
| --- Callback invoked after LSP "initialize", where `result` is a table of `capabilities`
 | |
| --- and anything else the server may send. For example, clangd sends
 | |
| --- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was sent to it.
 | |
| --- You can only modify the `client.offset_encoding` here before any notifications are sent.
 | |
| --- @field on_init? elem_or_list<fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)>
 | |
| ---
 | |
| --- Callback invoked on client exit.
 | |
| ---   - code: exit code of the process
 | |
| ---   - signal: number describing the signal used to terminate (if any)
 | |
| ---   - client_id: client handle
 | |
| --- @field on_exit? elem_or_list<fun(code: integer, signal: integer, client_id: integer)>
 | |
| ---
 | |
| --- Callback invoked when client attaches to a buffer.
 | |
| --- @field on_attach? elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>
 | |
| ---
 | |
| --- Passed directly to the language server in the initialize request. Invalid/empty values will
 | |
| --- (default: "off")
 | |
| --- @field trace? 'off'|'messages'|'verbose'
 | |
| ---
 | |
| --- A table with flags for the client. The current (experimental) flags are:
 | |
| --- @field flags? vim.lsp.Client.Flags
 | |
| ---
 | |
| --- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on initialization.
 | |
| --- @field root_dir? string
 | |
| 
 | |
| --- @class vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
 | |
| --- @field pending table<lsp.ProgressToken,lsp.LSPAny>
 | |
| 
 | |
| --- @class vim.lsp.Client
 | |
| ---
 | |
| --- The id allocated to the client.
 | |
| --- @field id integer
 | |
| ---
 | |
| --- If a name is specified on creation, that will be used. Otherwise it is just
 | |
| --- the client id. This is used for logs and messages.
 | |
| --- @field name string
 | |
| ---
 | |
| --- RPC client object, for low level interaction with the client.
 | |
| --- See |vim.lsp.rpc.start()|.
 | |
| --- @field rpc vim.lsp.rpc.PublicClient
 | |
| ---
 | |
| --- 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|.
 | |
| --- @field handlers table<string,lsp.Handler>
 | |
| ---
 | |
| --- The current pending requests in flight to the server. Entries are key-value
 | |
| --- pairs with the key being the request id while the value is a table with
 | |
| --- `type`, `bufnr`, and `method` key-value pairs. `type` is either "pending"
 | |
| --- 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}>
 | |
| ---
 | |
| --- copy of the table that was passed by the user
 | |
| --- to |vim.lsp.start_client()|.
 | |
| --- @field config vim.lsp.ClientConfig
 | |
| ---
 | |
| --- Response from the server sent on `initialize` describing the server's
 | |
| --- capabilities.
 | |
| --- @field server_capabilities lsp.ServerCapabilities?
 | |
| ---
 | |
| --- A ring buffer (|vim.ringbuf()|) containing progress messages
 | |
| --- sent by the server.
 | |
| --- @field progress vim.lsp.Client.Progress
 | |
| ---
 | |
| --- @field initialized true?
 | |
| ---
 | |
| --- The workspace folders configured in the client when the server starts.
 | |
| --- This property is only available if the client supports workspace folders.
 | |
| --- It can be `null` if the client supports workspace folders but none are
 | |
| --- configured.
 | |
| --- @field workspace_folders lsp.WorkspaceFolder[]?
 | |
| --- @field root_dir string?
 | |
| ---
 | |
| --- @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
 | |
| --- graceful shutdown
 | |
| --- @field private _graceful_shutdown_failed true?
 | |
| ---
 | |
| --- The initial trace setting. If omitted trace is disabled ("off").
 | |
| --- trace = "off" | "messages" | "verbose";
 | |
| --- @field private _trace 'off'|'messages'|'verbose'
 | |
| ---
 | |
| --- Table of command name to function which is called if any LSP action
 | |
| --- (code action, code lenses, ...) triggers the command.
 | |
| --- Client commands take precedence over the global command registry.
 | |
| --- @field commands table<string,fun(command: lsp.Command, ctx: table)>
 | |
| ---
 | |
| --- 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
 | |
| ---
 | |
| --- A table with flags for the client. The current (experimental) flags are:
 | |
| --- @field flags vim.lsp.Client.Flags
 | |
| ---
 | |
| --- @field get_language_id fun(bufnr: integer, filetype: string): string
 | |
| ---
 | |
| --- The capabilities provided by the client (editor or tool)
 | |
| --- @field capabilities lsp.ClientCapabilities
 | |
| --- @field private registrations table<string,lsp.Registration[]>
 | |
| --- @field dynamic_capabilities lsp.DynamicCapabilities
 | |
| ---
 | |
| --- @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)
 | |
| local Client = {}
 | |
| Client.__index = Client
 | |
| 
 | |
| --- @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
 | |
| 
 | |
| local client_index = 0
 | |
| 
 | |
| --- Checks whether a given path is a directory.
 | |
| --- @param filename (string) path to check
 | |
| --- @return boolean # true if {filename} exists and is a directory, false otherwise
 | |
| local function is_dir(filename)
 | |
|   validate('filename', filename, 'string')
 | |
|   local stat = uv.fs_stat(filename)
 | |
|   return stat and stat.type == 'directory' or false
 | |
| end
 | |
| 
 | |
| local valid_encodings = {
 | |
|   ['utf-8'] = 'utf-8',
 | |
|   ['utf-16'] = 'utf-16',
 | |
|   ['utf-32'] = 'utf-32',
 | |
|   ['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.
 | |
| --- @param encoding string? Encoding to normalize
 | |
| --- @return string # normalized encoding name
 | |
| local function validate_encoding(encoding)
 | |
|   validate('encoding', encoding, 'string', true)
 | |
|   if not encoding then
 | |
|     return valid_encodings.UTF16
 | |
|   end
 | |
|   return valid_encodings[encoding:lower()]
 | |
|     or error(
 | |
|       string.format(
 | |
|         "Invalid position encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
 | |
|         encoding
 | |
|       )
 | |
|     )
 | |
| end
 | |
| 
 | |
| --- Augments a validator function with support for optional (nil) values.
 | |
| --- @param fn (fun(v): boolean) The original validator function; should return a
 | |
| --- bool.
 | |
| --- @return fun(v): boolean # The augmented function. Also returns true if {v} is
 | |
| --- `nil`.
 | |
| local function optional_validator(fn)
 | |
|   return function(v)
 | |
|     return v == nil or fn(v)
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- By default, get_language_id just returns the exact filetype it is passed.
 | |
| --- It is possible to pass in something that will calculate a different filetype,
 | |
| --- to be sent by the client.
 | |
| --- @param _bufnr integer
 | |
| --- @param filetype string
 | |
| local function default_get_language_id(_bufnr, filetype)
 | |
|   return filetype
 | |
| end
 | |
| 
 | |
| --- Validates a client configuration as given to |vim.lsp.start_client()|.
 | |
| --- @param config vim.lsp.ClientConfig
 | |
| local function validate_config(config)
 | |
|   validate('config', config, 'table')
 | |
|   validate('handlers', config.handlers, 'table', true)
 | |
|   validate('capabilities', config.capabilities, 'table', true)
 | |
|   validate('cmd_cwd', config.cmd_cwd, optional_validator(is_dir), 'directory')
 | |
|   validate('cmd_env', config.cmd_env, 'table', true)
 | |
|   validate('detached', config.detached, 'boolean', true)
 | |
|   validate('name', config.name, 'string', true)
 | |
|   validate('on_error', config.on_error, 'function', true)
 | |
|   validate('on_exit', config.on_exit, { 'function', 'table' }, true)
 | |
|   validate('on_init', config.on_init, { 'function', 'table' }, true)
 | |
|   validate('on_attach', config.on_attach, { 'function', 'table' }, true)
 | |
|   validate('settings', config.settings, 'table', true)
 | |
|   validate('commands', config.commands, 'table', true)
 | |
|   validate('before_init', config.before_init, { 'function', 'table' }, true)
 | |
|   validate('offset_encoding', config.offset_encoding, 'string', true)
 | |
|   validate('flags', config.flags, 'table', true)
 | |
|   validate('get_language_id', config.get_language_id, 'function', true)
 | |
| 
 | |
|   assert(
 | |
|     (
 | |
|       not config.flags
 | |
|       or not config.flags.debounce_text_changes
 | |
|       or type(config.flags.debounce_text_changes) == 'number'
 | |
|     ),
 | |
|     'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
 | |
|   )
 | |
| end
 | |
| 
 | |
| --- @param trace string
 | |
| --- @return 'off'|'messages'|'verbose'
 | |
| local function get_trace(trace)
 | |
|   local valid_traces = {
 | |
|     off = 'off',
 | |
|     messages = 'messages',
 | |
|     verbose = 'verbose',
 | |
|   }
 | |
|   return trace and valid_traces[trace] or 'off'
 | |
| end
 | |
| 
 | |
| --- @param id integer
 | |
| --- @param config vim.lsp.ClientConfig
 | |
| --- @return string
 | |
| local function get_name(id, config)
 | |
|   local name = config.name
 | |
|   if name then
 | |
|     return name
 | |
|   end
 | |
| 
 | |
|   if type(config.cmd) == 'table' and config.cmd[1] then
 | |
|     return assert(vim.fs.basename(config.cmd[1]))
 | |
|   end
 | |
| 
 | |
|   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?
 | |
| function Client.create(config)
 | |
|   validate_config(config)
 | |
| 
 | |
|   client_index = client_index + 1
 | |
|   local id = client_index
 | |
|   local name = get_name(id, config)
 | |
| 
 | |
|   --- @class vim.lsp.Client
 | |
|   local self = {
 | |
|     id = id,
 | |
|     config = config,
 | |
|     handlers = config.handlers or {},
 | |
|     offset_encoding = validate_encoding(config.offset_encoding),
 | |
|     name = name,
 | |
|     _log_prefix = string.format('LSP[%s]', name),
 | |
|     requests = {},
 | |
|     attached_buffers = {},
 | |
|     server_capabilities = {},
 | |
|     registrations = {},
 | |
|     commands = config.commands or {},
 | |
|     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),
 | |
|     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_error_cb = config.on_error,
 | |
|     _trace = get_trace(config.trace),
 | |
| 
 | |
|     --- Contains $/progress report messages.
 | |
|     --- They have the format {token: integer|string, value: any}
 | |
|     --- For "work done progress", value will be one of:
 | |
|     --- - lsp.WorkDoneProgressBegin,
 | |
|     --- - lsp.WorkDoneProgressReport (extended with title from Begin)
 | |
|     --- - lsp.WorkDoneProgressEnd    (extended with title from Begin)
 | |
|     progress = vim.ringbuf(50) --[[@as vim.lsp.Client.Progress]],
 | |
| 
 | |
|     --- @deprecated use client.progress instead
 | |
|     messages = { name = name, messages = {}, progress = {}, status = {} },
 | |
|   }
 | |
| 
 | |
|   --- @class lsp.DynamicCapabilities
 | |
|   --- @nodoc
 | |
|   self.dynamic_capabilities = {
 | |
|     capabilities = self.registrations,
 | |
|     client_id = id,
 | |
|     register = function(_, registrations)
 | |
|       return self:_register_dynamic(registrations)
 | |
|     end,
 | |
|     unregister = function(_, unregistrations)
 | |
|       return self:_unregister_dynamic(unregistrations)
 | |
|     end,
 | |
|     get = function(_, method, opts)
 | |
|       return self:_get_registration(method, opts and opts.bufnr)
 | |
|     end,
 | |
|     supports_registration = function(_, method)
 | |
|       return self:_supports_registration(method)
 | |
|     end,
 | |
|     supports = function(_, method, opts)
 | |
|       return self:_get_registration(method, opts and opts.bufnr) ~= nil
 | |
|     end,
 | |
|   }
 | |
| 
 | |
|   --- @type table<string|integer, string> title of unfinished progress sequences by token
 | |
|   self.progress.pending = {}
 | |
| 
 | |
|   --- @type vim.lsp.rpc.Dispatchers
 | |
|   local dispatchers = {
 | |
|     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.
 | |
|   local config_cmd = config.cmd
 | |
|   if type(config_cmd) == 'function' then
 | |
|     self.rpc = config_cmd(dispatchers)
 | |
|   else
 | |
|     self.rpc = lsp.rpc.start(config_cmd, dispatchers, {
 | |
|       cwd = config.cmd_cwd,
 | |
|       env = config.cmd_env,
 | |
|       detached = config.detached,
 | |
|     })
 | |
|   end
 | |
| 
 | |
|   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
 | |
| 
 | |
| --- @private
 | |
| --- @param cbs function[]
 | |
| --- @param error_id integer
 | |
| --- @param ... any
 | |
| function Client:_run_callbacks(cbs, error_id, ...)
 | |
|   for _, cb in pairs(cbs) do
 | |
|     --- @type boolean, string?
 | |
|     local status, err = pcall(cb, ...)
 | |
|     if not status then
 | |
|       self:write_error(error_id, err)
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @nodoc
 | |
| function Client:initialize()
 | |
|   local config = self.config
 | |
| 
 | |
|   local root_uri --- @type string?
 | |
|   local root_path --- @type string?
 | |
|   if self.workspace_folders then
 | |
|     root_uri = self.workspace_folders[1].uri
 | |
|     root_path = vim.uri_to_fname(root_uri)
 | |
|   end
 | |
| 
 | |
|   local initialize_params = {
 | |
|     -- The process Id of the parent process that started the server. Is null if
 | |
|     -- the process has not been started by another process.  If the parent
 | |
|     -- process is not alive then the server should exit (see exit notification)
 | |
|     -- its process.
 | |
|     processId = uv.os_getpid(),
 | |
|     -- Information about the client
 | |
|     -- since 3.15.0
 | |
|     clientInfo = {
 | |
|       name = 'Neovim',
 | |
|       version = tostring(vim.version()),
 | |
|     },
 | |
|     -- The rootPath of the workspace. Is null if no folder is open.
 | |
|     --
 | |
|     -- @deprecated in favour of rootUri.
 | |
|     rootPath = root_path or vim.NIL,
 | |
|     -- The rootUri of the workspace. Is null if no folder is open. If both
 | |
|     -- `rootPath` and `rootUri` are set `rootUri` wins.
 | |
|     rootUri = root_uri or vim.NIL,
 | |
|     workspaceFolders = self.workspace_folders or vim.NIL,
 | |
|     -- User provided initialization options.
 | |
|     initializationOptions = config.init_options,
 | |
|     capabilities = self.capabilities,
 | |
|     trace = self._trace,
 | |
|     workDoneToken = '1',
 | |
|   }
 | |
| 
 | |
|   self:_run_callbacks(
 | |
|     { self._before_init_cb },
 | |
|     lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR,
 | |
|     initialize_params,
 | |
|     config
 | |
|   )
 | |
| 
 | |
|   log.trace(self._log_prefix, 'initialize_params', initialize_params)
 | |
| 
 | |
|   local rpc = self.rpc
 | |
| 
 | |
|   rpc.request('initialize', initialize_params, function(init_err, result)
 | |
|     assert(not init_err, tostring(init_err))
 | |
|     assert(result, 'server sent empty result')
 | |
|     rpc.notify('initialized', vim.empty_dict())
 | |
|     self.initialized = true
 | |
| 
 | |
|     -- These are the cleaned up capabilities we use for dynamically deciding
 | |
|     -- when to send certain events to clients.
 | |
|     self.server_capabilities =
 | |
|       assert(result.capabilities, "initialize result doesn't contain capabilities")
 | |
|     self.server_capabilities = assert(lsp.protocol.resolve_capabilities(self.server_capabilities))
 | |
| 
 | |
|     if self.server_capabilities.positionEncoding then
 | |
|       self.offset_encoding = self.server_capabilities.positionEncoding
 | |
|     end
 | |
| 
 | |
|     if next(self.settings) then
 | |
|       self:notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
 | |
|     end
 | |
| 
 | |
|     -- If server is being restarted, make sure to re-attach to any previously attached buffers.
 | |
|     -- Save which buffers before on_init in case new buffers are attached.
 | |
|     local reattach_bufs = vim.deepcopy(self.attached_buffers)
 | |
| 
 | |
|     self:_run_callbacks(self._on_init_cbs, lsp.client_errors.ON_INIT_CALLBACK_ERROR, self, result)
 | |
| 
 | |
|     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)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     log.info(
 | |
|       self._log_prefix,
 | |
|       'server_capabilities',
 | |
|       { server_capabilities = self.server_capabilities }
 | |
|     )
 | |
|   end)
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Returns the handler associated with an LSP method.
 | |
| --- Returns the default handler if the user hasn't set a custom one.
 | |
| ---
 | |
| --- @param method (string) LSP method name
 | |
| --- @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? 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()
 | |
|   end
 | |
|   return bufnr
 | |
| end
 | |
| 
 | |
| --- Sends a request to the server.
 | |
| ---
 | |
| --- This is a thin wrapper around {client.rpc.request} with some additional
 | |
| --- checks for capabilities and handler availability.
 | |
| ---
 | |
| --- @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 (default).
 | |
| --- @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)
 | |
|   if not handler then
 | |
|     handler = assert(
 | |
|       self:_resolve_handler(method),
 | |
|       string.format('not found: %q request handler for client %q.', method, self.name)
 | |
|     )
 | |
|   end
 | |
|   -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
 | |
|   changetracking.flush(self, bufnr)
 | |
|   bufnr = resolve_bufnr(bufnr)
 | |
|   local version = lsp.util.buf_versions[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 = {
 | |
|       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)
 | |
| 
 | |
|   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 },
 | |
|     })
 | |
|   end
 | |
| 
 | |
|   return success, request_id
 | |
| end
 | |
| 
 | |
| -- TODO(lewis6991): duplicated from lsp.lua
 | |
| local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
 | |
| 
 | |
| --- Concatenates and writes a list of strings to the Vim error buffer.
 | |
| ---
 | |
| --- @param ... string List to write to the buffer
 | |
| local function err_message(...)
 | |
|   local message = table.concat(vim.iter({ ... }):flatten():totable())
 | |
|   if vim.in_fast_event() then
 | |
|     vim.schedule(function()
 | |
|       api.nvim_err_writeln(message)
 | |
|       api.nvim_command('redraw')
 | |
|     end)
 | |
|   else
 | |
|     api.nvim_err_writeln(message)
 | |
|     api.nvim_command('redraw')
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- Sends a request to the server and synchronously waits for the response.
 | |
| ---
 | |
| --- This is a wrapper around |Client:request()|
 | |
| ---
 | |
| --- @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?, 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)
 | |
|   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)
 | |
|   if not success then
 | |
|     return nil
 | |
|   end
 | |
| 
 | |
|   local wait_result, reason = vim.wait(timeout_ms or 1000, function()
 | |
|     return request_result ~= nil
 | |
|   end, 10)
 | |
| 
 | |
|   if not wait_result then
 | |
|     if request_id then
 | |
|       self:cancel_request(request_id)
 | |
|     end
 | |
|     return nil, wait_result_reason[reason]
 | |
|   end
 | |
|   return request_result
 | |
| end
 | |
| 
 | |
| --- Sends a notification to an LSP server.
 | |
| ---
 | |
| --- @param method string LSP method name.
 | |
| --- @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
 | |
| 
 | |
|   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)
 | |
|   end
 | |
| 
 | |
|   return client_active
 | |
| end
 | |
| 
 | |
| --- Cancels a request with a given request id.
 | |
| ---
 | |
| --- @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)
 | |
|   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
 | |
|   return self.rpc.notify(ms.dollar_cancelRequest, { id = id })
 | |
| end
 | |
| 
 | |
| --- Stops a client, optionally with force.
 | |
| ---
 | |
| --- 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
 | |
| function Client:stop(force)
 | |
|   local rpc = self.rpc
 | |
| 
 | |
|   if rpc.is_closing() then
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   if force or not self.initialized or self._graceful_shutdown_failed then
 | |
|     rpc.terminate()
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   -- Sending a signal after a process has exited is acceptable.
 | |
|   rpc.request(ms.shutdown, nil, function(err, _)
 | |
|     if err == nil then
 | |
|       rpc.notify(ms.exit)
 | |
|     else
 | |
|       -- If there was an error in the shutdown request, then term to be safe.
 | |
|       rpc.terminate()
 | |
|       self._graceful_shutdown_failed = true
 | |
|     end
 | |
|     vim.lsp._watchfiles.cancel(self.id)
 | |
|   end)
 | |
| end
 | |
| 
 | |
| --- Get options for a method that is registered dynamically.
 | |
| --- @param method string
 | |
| function Client:_supports_registration(method)
 | |
|   local capability = vim.tbl_get(self.capabilities, unpack(vim.split(method, '/')))
 | |
|   return type(capability) == 'table' and capability.dynamicRegistration
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- @param registrations lsp.Registration[]
 | |
| function Client:_register_dynamic(registrations)
 | |
|   -- remove duplicates
 | |
|   self:_unregister_dynamic(registrations)
 | |
|   for _, reg in ipairs(registrations) do
 | |
|     local method = reg.method
 | |
|     if not self.registrations[method] then
 | |
|       self.registrations[method] = {}
 | |
|     end
 | |
|     table.insert(self.registrations[method], reg)
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @param registrations lsp.Registration[]
 | |
| function Client:_register(registrations)
 | |
|   self:_register_dynamic(registrations)
 | |
| 
 | |
|   local unsupported = {} --- @type string[]
 | |
| 
 | |
|   for _, reg in ipairs(registrations) do
 | |
|     local method = reg.method
 | |
|     if method == ms.workspace_didChangeWatchedFiles then
 | |
|       vim.lsp._watchfiles.register(reg, self.id)
 | |
|     elseif not self:_supports_registration(method) then
 | |
|       unsupported[#unsupported + 1] = method
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   if #unsupported > 0 then
 | |
|     local warning_tpl = 'The language server %s triggers a registerCapability '
 | |
|       .. 'handler for %s despite dynamicRegistration set to false. '
 | |
|       .. 'Report upstream, this warning is harmless'
 | |
|     log.warn(string.format(warning_tpl, self.name, table.concat(unsupported, ', ')))
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- @param unregistrations lsp.Unregistration[]
 | |
| function Client:_unregister_dynamic(unregistrations)
 | |
|   for _, unreg in ipairs(unregistrations) do
 | |
|     local sreg = self.registrations[unreg.method]
 | |
|     -- Unegister dynamic capability
 | |
|     for i, reg in ipairs(sreg or {}) do
 | |
|       if reg.id == unreg.id then
 | |
|         table.remove(sreg, i)
 | |
|         break
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @param unregistrations lsp.Unregistration[]
 | |
| function Client:_unregister(unregistrations)
 | |
|   self:_unregister_dynamic(unregistrations)
 | |
|   for _, unreg in ipairs(unregistrations) do
 | |
|     if unreg.method == ms.workspace_didChangeWatchedFiles then
 | |
|       vim.lsp._watchfiles.unregister(unreg, self.id)
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| function Client:_get_language_id(bufnr)
 | |
|   return self.get_language_id(bufnr, vim.bo[bufnr].filetype)
 | |
| end
 | |
| 
 | |
| --- @param method string
 | |
| --- @param bufnr? integer
 | |
| --- @return lsp.Registration?
 | |
| function Client:_get_registration(method, bufnr)
 | |
|   bufnr = bufnr or vim.api.nvim_get_current_buf()
 | |
|   for _, reg in ipairs(self.registrations[method] or {}) do
 | |
|     if not reg.registerOptions or not reg.registerOptions.documentSelector then
 | |
|       return reg
 | |
|     end
 | |
|     local documentSelector = reg.registerOptions.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
 | |
|       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))
 | |
|       then
 | |
|         return reg
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- 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()
 | |
|   return self.rpc.is_closing()
 | |
| end
 | |
| 
 | |
| --- Execute a lsp command, either via client command function (if available)
 | |
| --- or via workspace/executeCommand (if supported by the server)
 | |
| ---
 | |
| --- @param command lsp.Command
 | |
| --- @param context? {bufnr?: integer}
 | |
| --- @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.client_id = self.id
 | |
|   local cmdname = command.command
 | |
|   local fn = self.commands[cmdname] or lsp.commands[cmdname]
 | |
|   if fn then
 | |
|     fn(command, context)
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   local command_provider = self.server_capabilities.executeCommandProvider
 | |
|   local commands = type(command_provider) == 'table' and command_provider.commands or {}
 | |
| 
 | |
|   if not vim.list_contains(commands, cmdname) then
 | |
|     vim.notify_once(
 | |
|       string.format(
 | |
|         'Language server `%s` does not support command `%s`. This command may require a client extension.',
 | |
|         self.name,
 | |
|         cmdname
 | |
|       ),
 | |
|       vim.log.levels.WARN
 | |
|     )
 | |
|     return
 | |
|   end
 | |
|   -- Not using command directly to exclude extra properties,
 | |
|   -- see https://github.com/python-lsp/python-lsp-server/issues/146
 | |
|   --- @type lsp.ExecuteCommandParams
 | |
|   local params = {
 | |
|     command = cmdname,
 | |
|     arguments = command.arguments,
 | |
|   }
 | |
|   self:request(ms.workspace_executeCommand, params, handler, context.bufnr)
 | |
| end
 | |
| 
 | |
| --- Default handler for the 'textDocument/didOpen' LSP notification.
 | |
| ---
 | |
| --- @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
 | |
|     return
 | |
|   end
 | |
|   if not api.nvim_buf_is_loaded(bufnr) then
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   self:notify(ms.textDocument_didOpen, {
 | |
|     textDocument = {
 | |
|       version = lsp.util.buf_versions[bufnr],
 | |
|       uri = vim.uri_from_bufnr(bufnr),
 | |
|       languageId = self:_get_language_id(bufnr),
 | |
|       text = lsp._buf_get_full_text(bufnr),
 | |
|     },
 | |
|   })
 | |
| 
 | |
|   -- Next chance we get, we should re-do the diagnostics
 | |
|   vim.schedule(function()
 | |
|     -- Protect against a race where the buffer disappears
 | |
|     -- between `did_open_handler` and the scheduled function firing.
 | |
|     if api.nvim_buf_is_valid(bufnr) then
 | |
|       local namespace = lsp.diagnostic.get_namespace(self.id)
 | |
|       vim.diagnostic.show(namespace, bufnr)
 | |
|     end
 | |
|   end)
 | |
| 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)
 | |
|   self:_text_document_did_open_handler(bufnr)
 | |
| 
 | |
|   lsp._set_defaults(self, bufnr)
 | |
| 
 | |
|   api.nvim_exec_autocmds('LspAttach', {
 | |
|     buffer = bufnr,
 | |
|     modeline = false,
 | |
|     data = { client_id = self.id },
 | |
|   })
 | |
| 
 | |
|   self:_run_callbacks(self._on_attach_cbs, lsp.client_errors.ON_ATTACH_ERROR, self, bufnr)
 | |
| 
 | |
|   -- schedule the initialization of semantic tokens to give the above
 | |
|   -- on_attach and LspAttach callbacks the ability to schedule wrap the
 | |
|   -- opt-out (deleting the semanticTokensProvider from capabilities)
 | |
|   vim.schedule(function()
 | |
|     if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
 | |
|       lsp.semantic_tokens.start(bufnr, self.id)
 | |
|     end
 | |
|   end)
 | |
| 
 | |
|   self.attached_buffers[bufnr] = true
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Logs the given error to the LSP log and to the error buffer.
 | |
| --- @param code integer Error code
 | |
| --- @param err any Error arguments
 | |
| function Client:write_error(code, err)
 | |
|   local client_error = lsp.client_errors[code] --- @type string|integer
 | |
|   log.error(self._log_prefix, 'on_error', { code = client_error, err = err })
 | |
|   err_message(self._log_prefix, ': Error ', client_error, ': ', vim.inspect(err))
 | |
| end
 | |
| 
 | |
| --- 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 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
 | |
|     return true
 | |
|   end
 | |
|   if vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
 | |
|     return true
 | |
|   end
 | |
| 
 | |
|   local rmethod = lsp._resolve_to_request[method]
 | |
|   if rmethod then
 | |
|     if self:_supports_registration(rmethod) then
 | |
|       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, bufnr) ~= nil
 | |
|     end
 | |
|   end
 | |
|   return false
 | |
| end
 | |
| 
 | |
| --- Get options for a method that is registered dynamically.
 | |
| --- @param method string
 | |
| --- @param bufnr? integer
 | |
| --- @return lsp.LSPAny?
 | |
| function Client:_get_registration_options(method, bufnr)
 | |
|   if not self:_supports_registration(method) then
 | |
|     return
 | |
|   end
 | |
| 
 | |
|   local reg = self:_get_registration(method, bufnr)
 | |
| 
 | |
|   if reg then
 | |
|     return reg.registerOptions
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Handles a notification sent by an LSP server by invoking the
 | |
| --- corresponding handler.
 | |
| ---
 | |
| --- @param method string LSP method name
 | |
| --- @param params table The parameters for that method.
 | |
| function Client:_notification(method, params)
 | |
|   log.trace('notification', method, params)
 | |
|   local handler = self:_resolve_handler(method)
 | |
|   if handler then
 | |
|     -- Method name is provided here for convenience.
 | |
|     handler(nil, params, { method = method, client_id = self.id })
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Handles a request from an LSP server by invoking the corresponding handler.
 | |
| ---
 | |
| --- @param method (string) LSP method name
 | |
| --- @param params (table) The parameters for that method
 | |
| --- @return any result
 | |
| --- @return lsp.ResponseError error code and message set in case an exception happens during the request.
 | |
| function Client:_server_request(method, params)
 | |
|   log.trace('server_request', method, params)
 | |
|   local handler = self:_resolve_handler(method)
 | |
|   if handler then
 | |
|     log.trace('server_request: found handler for', method)
 | |
|     return handler(nil, params, { method = method, client_id = self.id })
 | |
|   end
 | |
|   log.warn('server_request: no handler found for', method)
 | |
|   return nil, lsp.rpc_response_error(lsp.protocol.ErrorCodes.MethodNotFound)
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Invoked when the client operation throws an error.
 | |
| ---
 | |
| --- @param code integer Error code
 | |
| --- @param err any Other arguments may be passed depending on the error kind
 | |
| --- @see vim.lsp.rpc.client_errors for possible errors. Use
 | |
| --- `vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
 | |
| function Client:_on_error(code, err)
 | |
|   self:write_error(code, err)
 | |
|   if self._on_error_cb then
 | |
|     --- @type boolean, string
 | |
|     local status, usererr = pcall(self._on_error_cb, code, err)
 | |
|     if not status then
 | |
|       log.error(self._log_prefix, 'user on_error failed', { err = usererr })
 | |
|       err_message(self._log_prefix, ' user on_error failed: ', tostring(usererr))
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| --- @private
 | |
| --- Invoked on client exit.
 | |
| ---
 | |
| --- @param code integer) exit code of the process
 | |
| --- @param signal integer the signal used to terminate (if any)
 | |
| function Client:_on_exit(code, signal)
 | |
|   self:_run_callbacks(
 | |
|     self._on_exit_cbs,
 | |
|     lsp.client_errors.ON_EXIT_CALLBACK_ERROR,
 | |
|     code,
 | |
|     signal,
 | |
|     self.id
 | |
|   )
 | |
| end
 | |
| 
 | |
| --- Add a directory to the workspace folders.
 | |
| --- @param dir string?
 | |
| function Client:_add_workspace_folder(dir)
 | |
|   for _, folder in pairs(self.workspace_folders or {}) do
 | |
|     if folder.name == dir then
 | |
|       print(dir, 'is already part of this workspace')
 | |
|       return
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   local wf = assert(get_workspace_folders(dir))
 | |
| 
 | |
|   self:notify(ms.workspace_didChangeWorkspaceFolders, {
 | |
|     event = { added = wf, removed = {} },
 | |
|   })
 | |
| 
 | |
|   if not self.workspace_folders then
 | |
|     self.workspace_folders = {}
 | |
|   end
 | |
|   vim.list_extend(self.workspace_folders, wf)
 | |
| end
 | |
| 
 | |
| --- Remove a directory to the workspace folders.
 | |
| --- @param dir string?
 | |
| function Client:_remove_workspace_folder(dir)
 | |
|   local wf = assert(get_workspace_folders(dir))
 | |
| 
 | |
|   self:notify(ms.workspace_didChangeWorkspaceFolders, {
 | |
|     event = { added = {}, removed = wf },
 | |
|   })
 | |
| 
 | |
|   for idx, folder in pairs(self.workspace_folders) do
 | |
|     if folder.name == dir then
 | |
|       table.remove(self.workspace_folders, idx)
 | |
|       break
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| return Client
 |