aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/diagnostic.lua56
-rw-r--r--runtime/lua/vim/lsp.lua62
-rw-r--r--runtime/lua/vim/lsp/sync.lua15
-rw-r--r--runtime/lua/vim/lsp/util.lua81
-rw-r--r--runtime/lua/vim/treesitter/query.lua22
5 files changed, 167 insertions, 69 deletions
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 59cb9f8c5b..742ebf69b2 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -91,23 +91,22 @@ local function filter_by_severity(severity, diagnostics)
end
---@private
-local function prefix_source(source, diagnostics)
- vim.validate { source = {source, function(v)
- return v == "always" or v == "if_many"
- end, "'always' or 'if_many'" } }
-
- if source == "if_many" then
- local sources = {}
- for _, d in pairs(diagnostics) do
- if d.source then
- sources[d.source] = true
+local function count_sources(bufnr)
+ local seen = {}
+ local count = 0
+ for _, namespace_diagnostics in pairs(diagnostic_cache[bufnr]) do
+ for _, diagnostic in ipairs(namespace_diagnostics) do
+ if diagnostic.source and not seen[diagnostic.source] then
+ seen[diagnostic.source] = true
+ count = count + 1
end
end
- if #vim.tbl_keys(sources) <= 1 then
- return diagnostics
- end
end
+ return count
+end
+---@private
+local function prefix_source(diagnostics)
return vim.tbl_map(function(d)
if not d.source then
return d
@@ -272,6 +271,8 @@ end
---@private
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
+ assert(diagnostic.lnum, "Diagnostic line number is required")
+ assert(diagnostic.col, "Diagnostic column is required")
diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
diagnostic.end_col = diagnostic.end_col or diagnostic.col
@@ -297,11 +298,6 @@ local function restore_extmarks(bufnr, last)
if not found[extmark[1]] then
local opts = extmark[4]
opts.id = extmark[1]
- -- HACK: end_row should be end_line
- if opts.end_row then
- opts.end_line = opts.end_row
- opts.end_row = nil
- end
pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
end
end
@@ -563,8 +559,10 @@ end
--- - virtual_text: (default true) Use virtual text for diagnostics. Options:
--- * severity: Only show virtual text for diagnostics matching the given
--- severity |diagnostic-severity|
---- * source: (string) Include the diagnostic source in virtual
---- text. One of "always" or "if_many".
+--- * source: (boolean or string) Include the diagnostic source in virtual
+--- text. Use "if_many" to only show sources if there is more than
+--- one diagnostic source in the buffer. Otherwise, any truthy value
+--- means to always show the diagnostic source.
--- * format: (function) A function that takes a diagnostic as input and
--- returns a string. The return value is the text used to display
--- the diagnostic. Example:
@@ -928,8 +926,11 @@ M.handlers.virtual_text = {
if opts.virtual_text.format then
diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
end
- if opts.virtual_text.source then
- diagnostics = prefix_source(opts.virtual_text.source, diagnostics)
+ if
+ opts.virtual_text.source
+ and (opts.virtual_text.source ~= "if_many" or count_sources(bufnr) > 1)
+ then
+ diagnostics = prefix_source(diagnostics)
end
if opts.virtual_text.severity then
severity = opts.virtual_text.severity
@@ -1154,8 +1155,11 @@ end
--- - header: (string or table) String to use as the header for the floating window. If a
--- table, it is interpreted as a [text, hl_group] tuple. Overrides the setting
--- from |vim.diagnostic.config()|.
---- - source: (string) Include the diagnostic source in the message. One of "always" or
---- "if_many". Overrides the setting from |vim.diagnostic.config()|.
+--- - source: (boolean or string) Include the diagnostic source in the message.
+--- Use "if_many" to only show sources if there is more than one source of
+--- diagnostics in the buffer. Otherwise, any truthy value means to always show
+--- the diagnostic source. Overrides the setting from
+--- |vim.diagnostic.config()|.
--- - format: (function) A function that takes a diagnostic as input and returns a
--- string. The return value is the text used to display the diagnostic.
--- Overrides the setting from |vim.diagnostic.config()|.
@@ -1267,8 +1271,8 @@ function M.open_float(opts, ...)
diagnostics = reformat_diagnostics(opts.format, diagnostics)
end
- if opts.source then
- diagnostics = prefix_source(opts.source, diagnostics)
+ if opts.source and (opts.source ~= "if_many" or count_sources(bufnr) > 1) then
+ diagnostics = prefix_source(diagnostics)
end
local prefix_opt = if_nil(opts.prefix, (scope == "cursor" and #diagnostics <= 1) and "" or function(_, i)
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 72a84dcc53..00839ec181 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -777,6 +777,10 @@ function lsp.start_client(config)
---@param code (number) exit code of the process
---@param signal (number) the signal used to terminate (if any)
function dispatch.on_exit(code, signal)
+ if config.on_exit then
+ pcall(config.on_exit, code, signal, client_id)
+ end
+
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
@@ -792,10 +796,6 @@ function lsp.start_client(config)
vim.notify(msg, vim.log.levels.WARN)
end)
end
-
- if config.on_exit then
- pcall(config.on_exit, code, signal, client_id)
- end
end
-- Start the RPC client.
@@ -1112,9 +1112,9 @@ local text_document_did_change_handler
do
text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline)
- -- Don't do anything if there are no clients attached.
+ -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
- return
+ return true
end
util.buf_versions[bufnr] = changedtick
local compute_change_and_notify = changetracking.prepare(bufnr, firstline, lastline, new_lastline)
@@ -1156,6 +1156,12 @@ function lsp.buf_attach_client(bufnr, client_id)
client_id = {client_id, 'n'};
}
bufnr = resolve_bufnr(bufnr)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ local _ = log.warn() and log.warn(
+ string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr)
+ )
+ return false
+ end
local buffer_client_ids = all_buffer_active_clients[bufnr]
-- This is our first time attaching to this buffer.
if not buffer_client_ids then
@@ -1214,6 +1220,50 @@ function lsp.buf_attach_client(bufnr, client_id)
return true
end
+--- Detaches client from the specified buffer.
+--- Note: While the server is notified that the text document (buffer)
+--- was closed, it is still able to send notifications should it ignore this notification.
+---
+---@param bufnr number Buffer handle, or 0 for current
+---@param client_id number Client id
+function lsp.buf_detach_client(bufnr, client_id)
+ validate {
+ bufnr = {bufnr, 'n', true};
+ client_id = {client_id, 'n'};
+ }
+ bufnr = resolve_bufnr(bufnr)
+
+ local client = lsp.get_client_by_id(client_id)
+ if not client or not client.attached_buffers[bufnr] then
+ vim.notify(
+ string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)
+ )
+ return
+ end
+
+ changetracking.reset_buf(client, bufnr)
+
+ if client.resolved_capabilities.text_document_open_close then
+ local uri = vim.uri_from_bufnr(bufnr)
+ local params = { textDocument = { uri = uri; } }
+ client.notify('textDocument/didClose', params)
+ end
+
+ client.attached_buffers[bufnr] = nil
+ util.buf_versions[bufnr] = nil
+
+ all_buffer_active_clients[bufnr][client_id] = nil
+ if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then
+ all_buffer_active_clients[bufnr] = nil
+ end
+
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
+
+ vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
+
+end
+
--- Checks if a buffer is attached for a particular client.
---
---@param bufnr (number) Buffer handle, or 0 for current
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index 5df2a4d144..d01f45ad8f 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -105,15 +105,16 @@ local function align_end_position(line, byte, offset_encoding)
char = compute_line_length(line, offset_encoding) + 1
else
-- Modifying line, find the nearest utf codepoint
- local offset = str_utf_end(line, byte)
+ local offset = str_utf_start(line, byte)
-- If the byte does not fall on the start of the character, then
-- align to the start of the next character.
- if offset > 0 then
- char = byte_to_utf(line, byte, offset_encoding) + 1
- byte = byte + offset
- else
+ if offset < 0 then
+ byte = byte + str_utf_end(line, byte) + 1
+ end
+ if byte <= #line then
char = byte_to_utf(line, byte, offset_encoding)
- byte = byte + offset
+ else
+ char = compute_line_length(line, offset_encoding) + 1
end
-- Extending line, find the nearest utf codepoint for the last valid character
end
@@ -167,7 +168,7 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline,
char_idx = compute_line_length(prev_line, offset_encoding) + 1
else
byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx)
- char_idx = byte_to_utf(prev_line, start_byte_idx, offset_encoding)
+ char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding)
end
-- Return the start difference (shared for new and prev lines)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index b11925df53..5921eb5bf0 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -97,15 +97,17 @@ end
---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
---@return number `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
- if encoding ~= 'utf-8' then
- local col32, col16 = vim.str_utfindex(line, index)
- if encoding == 'utf-32' then
- return col32
- else
- return col16
- end
+ if not encoding then encoding = 'utf-16' end
+ if encoding == 'utf-8' then
+ if index then return index else return #line end
+ elseif encoding == 'utf-16' then
+ local _, col16 = vim.str_utfindex(line, index)
+ return col16
+ elseif encoding == 'utf-32' then
+ local col32, _ = vim.str_utfindex(line, index)
+ return col32
else
- return index
+ error("Invalid encoding: " .. vim.inspect(encoding))
end
end
@@ -117,10 +119,15 @@ end
---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
---@return number byte (utf-8) index of `encoding` index `index` in `line`
function M._str_byteindex_enc(line, index, encoding)
- if encoding ~= 'utf-8' then
- return vim.str_byteindex(line, index, not encoding or encoding ~= 'utf-32')
+ if not encoding then encoding = 'utf-16' end
+ if encoding == 'utf-8' then
+ if index then return index else return #line end
+ elseif encoding == 'utf-16' then
+ return vim.str_byteindex(line, index, true)
+ elseif encoding == 'utf-32' then
+ return vim.str_byteindex(line, index)
else
- return index
+ error("Invalid encoding: " .. vim.inspect(encoding))
end
end
@@ -1253,17 +1260,57 @@ function M.stylize_markdown(bufnr, contents, opts)
return stripped
end
+---@private
--- Creates autocommands to close a preview window when events happen.
---
----@param events (table) list of events
----@param winnr (number) window id of preview window
+---@param events table list of events
+---@param winnr number window id of preview window
+---@param bufnrs table list of buffers where the preview window will remain visible
---@see |autocmd-events|
-function M.close_preview_autocmd(events, winnr)
+local function close_preview_autocmd(events, winnr, bufnrs)
+ local augroup = 'preview_window_'..winnr
+
+ -- close the preview window when entered a buffer that is not
+ -- the floating window buffer or the buffer that spawned it
+ vim.cmd(string.format([[
+ augroup %s
+ autocmd!
+ autocmd BufEnter * lua vim.lsp.util._close_preview_window(%d, {%s})
+ augroup end
+ ]], augroup, winnr, table.concat(bufnrs, ',')))
+
if #events > 0 then
- api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+ vim.cmd(string.format([[
+ augroup %s
+ autocmd %s <buffer> lua vim.lsp.util._close_preview_window(%d)
+ augroup end
+ ]], augroup, table.concat(events, ','), winnr))
end
end
+---@private
+--- Closes the preview window
+---
+---@param winnr number window id of preview window
+---@param bufnrs table|nil optional list of ignored buffers
+function M._close_preview_window(winnr, bufnrs)
+ vim.schedule(function()
+ -- exit if we are in one of ignored buffers
+ if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then
+ return
+ end
+
+ local augroup = 'preview_window_'..winnr
+ vim.cmd(string.format([[
+ augroup %s
+ autocmd!
+ augroup end
+ augroup! %s
+ ]], augroup, augroup))
+ pcall(vim.api.nvim_win_close, winnr, true)
+ end)
+end
+
---@internal
--- Computes size of float needed to show contents (with optional wrapping)
---
@@ -1370,7 +1417,7 @@ function M.open_floating_preview(contents, syntax, opts)
opts.wrap = opts.wrap ~= false -- wrapping by default
opts.stylize_markdown = opts.stylize_markdown ~= false
opts.focus = opts.focus ~= false
- opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}
+ opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "InsertCharPre"}
local bufnr = api.nvim_get_current_buf()
@@ -1439,7 +1486,7 @@ function M.open_floating_preview(contents, syntax, opts)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true, nowait = true})
- M.close_preview_autocmd(opts.close_events, floating_winnr)
+ close_preview_autocmd(opts.close_events, floating_winnr, {floating_bufnr, bufnr})
-- save focus_id
if opts.focus_id then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 5fa45289d8..ebed502c92 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -167,30 +167,31 @@ end
--- Gets the text corresponding to a given node
---
---@param node the node
----@param bsource The buffer or string from which the node is extracted
+---@param source The buffer or string from which the node is extracted
function M.get_node_text(node, source)
local start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_()
if type(source) == "number" then
local lines
- local eof_row = vim.api.nvim_buf_line_count(source)
+ local eof_row = a.nvim_buf_line_count(source)
if start_row >= eof_row then
return nil
end
+
if end_col == 0 then
lines = a.nvim_buf_get_lines(source, start_row, end_row, true)
- end_col = #lines[#lines]
+ end_col = -1
else
lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
end
- lines[1] = string.sub(lines[1], start_col + 1)
- local end_index = end_col
if #lines == 1 then
- end_index = end_col - start_col
+ lines[1] = string.sub(lines[1], start_col+1, end_col)
+ else
+ lines[1] = string.sub(lines[1], start_col+1)
+ lines[#lines] = string.sub(lines[#lines], 1, end_col)
end
- lines[#lines] = string.sub(lines[#lines], 1, end_index)
return table.concat(lines, "\n")
elseif type(source) == "string" then
@@ -247,13 +248,8 @@ local predicate_handlers = {
return function(match, _, source, pred)
local node = match[pred[2]]
- local start_row, start_col, end_row, end_col = node:range()
- if start_row ~= end_row then
- return false
- end
-
local regex = compiled_vim_regexes[pred[3]]
- return regex:match_line(source, start_row, start_col, end_col)
+ return regex:match_str(M.get_node_text(node, source))
end
end)(),