aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/vim/lsp.lua61
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua73
-rw-r--r--runtime/lua/vim/lsp/handlers.lua13
-rw-r--r--runtime/lua/vim/lsp/log.lua4
-rw-r--r--runtime/lua/vim/lsp/util.lua26
-rw-r--r--runtime/lua/vim/treesitter.lua2
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua18
-rw-r--r--runtime/lua/vim/treesitter/query.lua19
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()