diff options
Diffstat (limited to 'runtime/lua')
-rw-r--r-- | runtime/lua/vim/lsp.lua | 61 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 73 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 13 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 26 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 19 |
8 files changed, 156 insertions, 60 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0326550245..a6f118abde 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -121,6 +121,9 @@ local active_clients = {} local all_buffer_active_clients = {} local uninitialized_clients = {} +-- Tracks all buffers attached to a client. +local all_client_active_buffers = {} + --@private --- Invokes a function for each LSP client attached to the buffer {bufnr}. --- @@ -226,6 +229,7 @@ local function validate_client_config(config) on_error = { config.on_error, "f", true }; on_exit = { config.on_exit, "f", true }; on_init = { config.on_init, "f", true }; + settings = { config.settings, "t", true }; before_init = { config.before_init, "f", true }; offset_encoding = { config.offset_encoding, "s", true }; flags = { config.flags, "t", true }; @@ -327,8 +331,9 @@ end --- Checks whether a client is stopped. --- Returns: true if the client is fully stopped. --- ---- - on_attach(bufnr) +--- - on_attach(client, bufnr) --- Runs the on_attach function from the client's config if it was defined. +--- Useful for buffer-local setup. --- --- - Members --- - {id} (number): The id allocated to the client. @@ -398,6 +403,10 @@ end --- --@param handlers Map of language server method names to |lsp-handler| --- +--@param settings Map with language server specific settings. These are +--- returned to the language server if requested via `workspace/configuration`. +--- Keys are case-sensitive. +--- --@param init_options Values to pass in the initialization request --- as `initializationOptions`. See `initialize` in the LSP spec. --- @@ -424,7 +433,10 @@ end --- 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. +--- any notifications are sent. Most language servers expect to be sent client specified settings after +--- initialization. Neovim does not make this assumption. A +--- `workspace/didChangeConfiguration` notification should be sent +--- to the server during on_init. --- --@param on_exit Callback (code, signal, client_id) invoked on client --- exit. @@ -457,6 +469,7 @@ function lsp.start_client(config) local cmd, cmd_args, offset_encoding = cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding config.flags = config.flags or {} + config.settings = config.settings or {} local client_id = next_client_id() @@ -581,12 +594,19 @@ function lsp.start_client(config) local valid_traces = { off = 'off'; messages = 'messages'; verbose = 'verbose'; } + local version = vim.version() 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.getpid(); + -- Information about the client + -- since 3.15.0 + clientInfo = { + name = "Neovim", + version = string.format("%s.%s.%s", version.major, version.minor, version.patch) + }; -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. @@ -871,9 +891,7 @@ end function lsp._text_document_did_save_handler(bufnr) bufnr = resolve_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr) - local text = once(function() - return table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n') - end) + local text = once(buf_get_full_text) for_each_buffer_client(bufnr, function(client, _client_id) if client.resolved_capabilities.text_document_save then local included_text @@ -916,7 +934,7 @@ function lsp.buf_attach_client(bufnr, client_id) on_lines = text_document_did_change_handler; on_detach = function() local params = { textDocument = { uri = uri; } } - for_each_buffer_client(bufnr, function(client, _client_id) + for_each_buffer_client(bufnr, function(client, _) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end @@ -930,6 +948,13 @@ function lsp.buf_attach_client(bufnr, client_id) utf_sizes = true; }) end + + if not all_client_active_buffers[client_id] then + all_client_active_buffers[client_id] = {} + end + + table.insert(all_client_active_buffers[client_id], bufnr) + if buffer_client_ids[client_id] then return end -- This is our first time attaching this client to this buffer. buffer_client_ids[client_id] = true @@ -961,6 +986,19 @@ function lsp.get_client_by_id(client_id) return active_clients[client_id] or uninitialized_clients[client_id] end +--- Returns list of buffers attached to client_id. +-- +--@param client_id client id +--@returns list of buffer ids +function lsp.get_buffers_by_client_id(client_id) + local active_client_buffers = all_client_active_buffers[client_id] + if active_client_buffers then + return active_client_buffers + else + return {} + end +end + --- Stops a client(s). --- --- You can also use the `stop()` function on a |vim.lsp.client| object. @@ -978,12 +1016,23 @@ end function lsp.stop_client(client_id, force) local ids = type(client_id) == 'table' and client_id or {client_id} for _, id in ipairs(ids) do + local resolved_client_id if type(id) == 'table' and id.stop ~= nil then id.stop(force) + resolved_client_id = id.id elseif active_clients[id] then active_clients[id].stop(force) + resolved_client_id = id elseif uninitialized_clients[id] then uninitialized_clients[id].stop(true) + resolved_client_id = id + end + if resolved_client_id then + local client_buffers = lsp.get_buffers_by_client_id(resolved_client_id) + for idx = 1, #client_buffers do + lsp.diagnostic.clear(client_buffers[idx], resolved_client_id) + end + all_client_active_buffers[resolved_client_id] = nil end end end diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 072349b226..a625098bab 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -16,6 +16,24 @@ local to_severity = function(severity) return type(severity) == 'string' and DiagnosticSeverity[severity] or severity end +local filter_to_severity_limit = function(severity, diagnostics) + local filter_level = to_severity(severity) + if not filter_level then + return diagnostics + end + + return vim.tbl_filter(function(t) return t.severity == filter_level end, diagnostics) +end + +local filter_by_severity_limit = function(severity_limit, diagnostics) + local filter_level = to_severity(severity_limit) + if not filter_level then + return diagnostics + end + + return vim.tbl_filter(function(t) return t.severity <= filter_level end, diagnostics) +end + local to_position = function(position, bufnr) vim.validate { position = {position, 't'} } @@ -377,11 +395,9 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) end if opts.severity then - local filter_level = to_severity(opts.severity) - line_diagnostics = vim.tbl_filter(function(t) return t.severity == filter_level end, line_diagnostics) + line_diagnostics = filter_to_severity_limit(opts.severity, line_diagnostics) elseif opts.severity_limit then - local filter_level = to_severity(opts.severity_limit) - line_diagnostics = vim.tbl_filter(function(t) return t.severity <= filter_level end, line_diagnostics) + line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) end if opts.severity_sort then @@ -542,7 +558,7 @@ function M.goto_prev(opts) ) end ---- Get the previous diagnostic closest to the cursor_position +--- Get the next diagnostic closest to the cursor_position ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic function M.get_next(opts) @@ -609,6 +625,8 @@ end ---@param sign_ns number|nil ---@param opts table Configuration for signs. Keys: --- - priority: Set the priority of the signs. +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) opts = opts or {} sign_ns = sign_ns or M._get_sign_namespace(client_id) @@ -622,9 +640,11 @@ function M.set_signs(diagnostics, bufnr, client_id, sign_ns, opts) end bufnr = get_bufnr(bufnr) + diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) local ok = true for _, diagnostic in ipairs(diagnostics) do + ok = ok and pcall(vim.fn.sign_place, 0, sign_ns, @@ -654,15 +674,17 @@ end --- </pre> --- ---@param diagnostics Diagnostic[] ----@param bufnr number The buffer number ----@param client_id number the client id ----@param diagnostic_ns number|nil ----@param opts table Currently unused. +---@param bufnr number: The buffer number +---@param client_id number: The client id +---@param diagnostic_ns number|nil: The namespace +---@param opts table: Configuration table: +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_underline(diagnostics, bufnr, client_id, diagnostic_ns, opts) opts = opts or {} - assert(opts) -- lint diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id) + diagnostics = filter_by_severity_limit(opts.severity_limit, diagnostics) for _, diagnostic in ipairs(diagnostics) do local start = diagnostic.range["start"] @@ -703,6 +725,8 @@ end ---@param opts table Options on how to display virtual text. Keys: --- - prefix (string): Prefix to display before virtual text on line --- - spacing (number): Number of spaces to insert before virtual text +--- - severity_limit (DiagnosticSeverity): +--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) opts = opts or {} @@ -721,6 +745,7 @@ function M.set_virtual_text(diagnostics, bufnr, client_id, diagnostic_ns, opts) end for line, line_diagnostics in pairs(buffer_line_diagnostics) do + line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics) local virt_texts = M.get_virtual_text_chunks_for_line(bufnr, line, line_diagnostics, opts) if virt_texts then @@ -1082,7 +1107,7 @@ end ---@param bufnr number The buffer number ---@param line_nr number The line number ---@param client_id number|nil the client id ----@return {popup_bufnr, win_id} +---@return table {popup_bufnr, win_id} function M.show_line_diagnostics(opts, bufnr, line_nr, client_id) opts = opts or {} opts.severity_sort = if_nil(opts.severity_sort, true) @@ -1151,30 +1176,14 @@ function M.set_loclist(opts) local bufnr = vim.api.nvim_get_current_buf() local buffer_diags = M.get(bufnr, opts.client_id) - local severity = to_severity(opts.severity) - local severity_limit = to_severity(opts.severity_limit) + if opts.severity then + buffer_diags = filter_to_severity_limit(opts.severity, buffer_diags) + elseif opts.severity_limit then + buffer_diags = filter_by_severity_limit(opts.severity_limit, buffer_diags) + end local items = {} local insert_diag = function(diag) - if severity then - -- Handle missing severities - if not diag.severity then - return - end - - if severity ~= diag.severity then - return - end - elseif severity_limit then - if not diag.severity then - return - end - - if severity_limit < diag.severity then - return - end - end - local pos = diag.range.start local row = pos.line local col = util.character_offset(bufnr, row, pos.character) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 87f35363b1..7eac3febd9 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -97,6 +97,18 @@ M['window/showMessageRequest'] = function(_, _, params) end end +--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability +M['client/registerCapability'] = function(_, _, _, client_id) + local warning_tpl = "The language server %s triggers a registerCapability ".. + "handler despite dynamicRegistration set to false. ".. + "Report upstream, this warning is harmless" + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format("id=%d", client_id) + local warning = string.format(warning_tpl, client_name) + log.warn(warning) + return vim.NIL +end + --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction M['textDocument/codeAction'] = function(_, _, actions) if actions == nil or vim.tbl_isempty(actions) then @@ -150,6 +162,7 @@ M['workspace/configuration'] = function(err, _, params, client_id) local client = vim.lsp.get_client_by_id(client_id) if not client then err_message("LSP[id=", client_id, "] client has shut down after sending the message") + return end if err then error(vim.inspect(err)) end if not params.items then diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 587a65cd96..b6e91e37b9 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -28,7 +28,7 @@ do local function path_join(...) return table.concat(vim.tbl_flatten{...}, path_sep) end - local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log') + local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log') --- Returns the log filename. --@returns (string) log filename @@ -36,7 +36,7 @@ do return logfilename end - vim.fn.mkdir(vim.fn.stdpath('data'), "p") + vim.fn.mkdir(vim.fn.stdpath('cache'), "p") local logfile = assert(io.open(logfilename, "a+")) for level, levelnr in pairs(log.levels) do -- Also export the log level on the root object. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ecff95f61e..00bdeecef3 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -254,19 +254,27 @@ function M.extract_completion_items(result) end --- Applies a `TextDocumentEdit`, which is a list of changes to a single --- document. +--- document. --- ---@param text_document_edit (table) a `TextDocumentEdit` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit) +---@param text_document_edit table: a `TextDocumentEdit` object +---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) +---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit +function M.apply_text_document_edit(text_document_edit, index) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + -- For lists of text document edits, + -- do not check the version after the first edit. + local should_check_version = true + if index and index > 1 then + should_check_version = false + end + -- `VersionedTextDocumentIdentifier`s version may be null -- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier - if text_document.version + if should_check_version and (text_document.version and M.buf_versions[bufnr] - and M.buf_versions[bufnr] > text_document.version then + and M.buf_versions[bufnr] > text_document.version) then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -459,12 +467,12 @@ end -- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit function M.apply_workspace_edit(workspace_edit) if workspace_edit.documentChanges then - for _, change in ipairs(workspace_edit.documentChanges) do + for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind then -- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change) + M.apply_text_document_edit(change, idx) end end return @@ -718,7 +726,7 @@ function M.focusable_float(unique_name, fn) local bufnr = api.nvim_get_current_buf() do local win = find_window_by_var(unique_name, bufnr) - if win and api.nvim_win_is_valid(win) and not vim.fn.pumvisible() then + if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then api.nvim_set_current_win(win) api.nvim_command("stopinsert") return diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 79dcf77f9e..38ac182e32 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -50,7 +50,7 @@ function M._create_parser(bufnr, lang, opts) end end - a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true}) + a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, preview=true}) self:parse() diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 9c620c422c..c864fe5878 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -425,23 +425,21 @@ function LanguageTree:register_cbs(cbs) end end -local function region_contains(region, range) - for _, node in ipairs(region) do - local start_row, start_col, end_row, end_col = node:range() - local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) - local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) +local function tree_contains(tree, range) + local start_row, start_col, end_row, end_col = tree:root():range() + local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) + local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) - if start_fits and end_fits then - return true - end + if start_fits and end_fits then + return true end return false end function LanguageTree:contains(range) - for _, region in pairs(self._regions) do - if region_contains(region, range) then + for _, tree in pairs(self._trees) do + if tree_contains(tree, range) then return true end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5a27d740a2..e49f54681d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -340,6 +340,19 @@ function Query:apply_directives(match, pattern, source, metadata) end end + +--- Returns the start and stop value if set else the node's range. +-- When the node's range is used, the stop is incremented by 1 +-- to make the search inclusive. +local function value_or_node_range(start, stop, node) + if start == nil and stop == nil then + local node_start, _, node_stop, _ = node:range() + return node_start, node_stop + 1 -- Make stop inclusive + end + + return start, stop +end + --- Iterates of the captures of self on a given range. -- -- @param node The node under witch the search will occur @@ -353,6 +366,9 @@ function Query:iter_captures(node, source, start, stop) if type(source) == "number" and source == 0 then source = vim.api.nvim_get_current_buf() end + + start, stop = value_or_node_range(start, stop, node) + local raw_iter = node:_rawquery(self.query, true, start, stop) local function iter() local capture, captured_node, match = raw_iter() @@ -385,6 +401,9 @@ function Query:iter_matches(node, source, start, stop) if type(source) == "number" and source == 0 then source = vim.api.nvim_get_current_buf() end + + start, stop = value_or_node_range(start, stop, node) + local raw_iter = node:_rawquery(self.query, false, start, stop) local function iter() local pattern, match = raw_iter() |