aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/diagnostic.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:55 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:55 +0000
commit067dc73729267c0262438a6fdd66e586f8496946 (patch)
treed2e832f1a08083fd1457aaba9a774e72d69e5266 /runtime/lua/vim/diagnostic.lua
parentcd16d3df4c2a21ada895a1353712969045e5c728 (diff)
parent3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff)
downloadrneovim-067dc73729267c0262438a6fdd66e586f8496946.tar.gz
rneovim-067dc73729267c0262438a6fdd66e586f8496946.tar.bz2
rneovim-067dc73729267c0262438a6fdd66e586f8496946.zip
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'runtime/lua/vim/diagnostic.lua')
-rw-r--r--runtime/lua/vim/diagnostic.lua358
1 files changed, 216 insertions, 142 deletions
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 3f71d4f70d..6fd000a029 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -1,7 +1,8 @@
-local if_nil = vim.F.if_nil
+local api, if_nil = vim.api, vim.F.if_nil
local M = {}
+---@enum DiagnosticSeverity
M.severity = {
ERROR = 1,
WARN = 2,
@@ -45,18 +46,24 @@ local bufnr_and_namespace_cacher_mt = {
end,
}
-local diagnostic_cache = setmetatable({}, {
- __index = function(t, bufnr)
- assert(bufnr > 0, 'Invalid buffer number')
- vim.api.nvim_buf_attach(bufnr, false, {
- on_detach = function()
- rawset(t, bufnr, nil) -- clear cache
- end,
- })
- t[bufnr] = {}
- return t[bufnr]
- end,
-})
+local diagnostic_cache
+do
+ local group = api.nvim_create_augroup('DiagnosticBufWipeout', {})
+ diagnostic_cache = setmetatable({}, {
+ __index = function(t, bufnr)
+ assert(bufnr > 0, 'Invalid buffer number')
+ api.nvim_create_autocmd('BufWipeout', {
+ group = group,
+ buffer = bufnr,
+ callback = function()
+ rawset(t, bufnr, nil)
+ end,
+ })
+ t[bufnr] = {}
+ return t[bufnr]
+ end,
+ })
+end
local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt)
local diagnostic_attached_buffers = {}
@@ -239,25 +246,12 @@ end)()
---@private
local function get_bufnr(bufnr)
if not bufnr or bufnr == 0 then
- return vim.api.nvim_get_current_buf()
+ return api.nvim_get_current_buf()
end
return bufnr
end
---@private
-local function is_disabled(namespace, bufnr)
- local ns = M.get_namespace(namespace)
- if ns.disabled then
- return true
- end
-
- if type(diagnostic_disabled[bufnr]) == 'table' then
- return diagnostic_disabled[bufnr][namespace]
- end
- return diagnostic_disabled[bufnr]
-end
-
----@private
local function diagnostic_lines(diagnostics)
if not diagnostics then
return {}
@@ -293,7 +287,7 @@ end
---@private
local function restore_extmarks(bufnr, last)
for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
- local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
+ local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
local found = {}
for _, extmark in ipairs(extmarks_current) do
-- nvim_buf_set_lines will move any extmark to the line after the last
@@ -306,7 +300,7 @@ local function restore_extmarks(bufnr, last)
if not found[extmark[1]] then
local opts = extmark[4]
opts.id = extmark[1]
- pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
+ pcall(api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
end
end
end
@@ -316,7 +310,7 @@ end
local function save_extmarks(namespace, bufnr)
bufnr = get_bufnr(bufnr)
if not diagnostic_attached_buffers[bufnr] then
- vim.api.nvim_buf_attach(bufnr, false, {
+ api.nvim_buf_attach(bufnr, false, {
on_lines = function(_, _, _, _, _, last)
restore_extmarks(bufnr, last - 1)
end,
@@ -327,7 +321,7 @@ local function save_extmarks(namespace, bufnr)
diagnostic_attached_buffers[bufnr] = true
end
diagnostic_cache_extmarks[bufnr][namespace] =
- vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
+ api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
end
local registered_autocmds = {}
@@ -351,19 +345,6 @@ local function execute_scheduled_display(namespace, bufnr)
M.show(namespace, bufnr, nil, args)
end
---- @deprecated
---- Callback scheduled when leaving Insert mode.
----
---- called from the Vimscript autocommand.
----
---- See @ref schedule_display()
----
----@private
-function M._execute_scheduled_display(namespace, bufnr)
- vim.deprecate('vim.diagnostic._execute_scheduled_display', nil, '0.9')
- execute_scheduled_display(namespace, bufnr)
-end
-
--- Table of autocmd events to fire the update for displaying new diagnostic information
local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
@@ -373,8 +354,8 @@ local function schedule_display(namespace, bufnr, args)
local key = make_augroup_key(namespace, bufnr)
if not registered_autocmds[key] then
- local group = vim.api.nvim_create_augroup(key, { clear = true })
- vim.api.nvim_create_autocmd(insert_leave_auto_cmds, {
+ local group = api.nvim_create_augroup(key, { clear = true })
+ api.nvim_create_autocmd(insert_leave_auto_cmds, {
group = group,
buffer = bufnr,
callback = function()
@@ -391,7 +372,7 @@ local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr)
if registered_autocmds[key] then
- vim.api.nvim_del_augroup_by_name(key)
+ api.nvim_del_augroup_by_name(key)
registered_autocmds[key] = nil
end
end
@@ -406,7 +387,7 @@ local function get_diagnostics(bufnr, opts, clamp)
-- Memoized results of buf_line_count per bufnr
local buf_line_count = setmetatable({}, {
__index = function(t, k)
- t[k] = vim.api.nvim_buf_line_count(k)
+ t[k] = api.nvim_buf_line_count(k)
return rawget(t, k)
end,
})
@@ -414,7 +395,7 @@ local function get_diagnostics(bufnr, opts, clamp)
---@private
local function add(b, d)
if not opts.lnum or d.lnum == opts.lnum then
- if clamp and vim.api.nvim_buf_is_loaded(b) then
+ if clamp and api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1
if
d.lnum > line_count
@@ -435,32 +416,31 @@ local function get_diagnostics(bufnr, opts, clamp)
end
end
+ ---@private
+ local function add_all_diags(buf, diags)
+ for _, diagnostic in pairs(diags) do
+ add(buf, diagnostic)
+ end
+ end
+
if namespace == nil and bufnr == nil then
for b, t in pairs(diagnostic_cache) do
for _, v in pairs(t) do
- for _, diagnostic in pairs(v) do
- add(b, diagnostic)
- end
+ add_all_diags(b, v)
end
end
elseif namespace == nil then
bufnr = get_bufnr(bufnr)
for iter_namespace in pairs(diagnostic_cache[bufnr]) do
- for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do
- add(bufnr, diagnostic)
- end
+ add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace])
end
elseif bufnr == nil then
for b, t in pairs(diagnostic_cache) do
- for _, diagnostic in pairs(t[namespace] or {}) do
- add(b, diagnostic)
- end
+ add_all_diags(b, t[namespace] or {})
end
else
bufnr = get_bufnr(bufnr)
- for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do
- add(bufnr, diagnostic)
- end
+ add_all_diags(bufnr, diagnostic_cache[bufnr][namespace] or {})
end
if opts.severity then
@@ -478,7 +458,7 @@ local function set_list(loclist, opts)
local winnr = opts.winnr or 0
local bufnr
if loclist then
- bufnr = vim.api.nvim_win_get_buf(winnr)
+ bufnr = api.nvim_win_get_buf(winnr)
end
-- Don't clamp line numbers since the quickfix list can already handle line
-- numbers beyond the end of the buffer
@@ -490,7 +470,7 @@ local function set_list(loclist, opts)
vim.fn.setqflist({}, ' ', { title = title, items = items })
end
if open then
- vim.api.nvim_command(loclist and 'lopen' or 'botright copen')
+ api.nvim_command(loclist and 'lwindow' or 'botright cwindow')
end
end
@@ -499,7 +479,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
position[1] = position[1] - 1
bufnr = get_bufnr(bufnr)
local wrap = vim.F.if_nil(opts.wrap, true)
- local line_count = vim.api.nvim_buf_line_count(bufnr)
+ local line_count = api.nvim_buf_line_count(bufnr)
local diagnostics =
get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true)
local line_diagnostics = diagnostic_lines(diagnostics)
@@ -513,7 +493,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
lnum = (lnum + line_count) % line_count
end
if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
- local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
+ local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
local sort_diagnostics, is_next
if search_forward then
sort_diagnostics = function(a, b)
@@ -549,17 +529,17 @@ local function diagnostic_move_pos(opts, pos)
opts = opts or {}
local float = vim.F.if_nil(opts.float, true)
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
+ local win_id = opts.win_id or api.nvim_get_current_win()
if not pos then
- vim.api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {})
+ api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {})
return
end
- vim.api.nvim_win_call(win_id, function()
+ api.nvim_win_call(win_id, function()
-- Save position in the window's jumplist
vim.cmd("normal! m'")
- vim.api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] })
+ api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] })
-- Open folds under the cursor
vim.cmd('normal! zv')
end)
@@ -568,7 +548,7 @@ local function diagnostic_move_pos(opts, pos)
local float_opts = type(float) == 'table' and float or {}
vim.schedule(function()
M.open_float(vim.tbl_extend('keep', float_opts, {
- bufnr = vim.api.nvim_win_get_buf(win_id),
+ bufnr = api.nvim_win_get_buf(win_id),
scope = 'cursor',
focus = false,
}))
@@ -585,12 +565,12 @@ end
--- followed by namespace configuration, and finally global configuration.
---
--- For example, if a user enables virtual text globally with
---- <pre>
+--- <pre>lua
--- vim.diagnostic.config({ virtual_text = true })
--- </pre>
---
--- and a diagnostic producer sets diagnostics with
---- <pre>
+--- <pre>lua
--- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
--- </pre>
---
@@ -620,16 +600,20 @@ end
--- * spacing: (number) Amount of empty spaces inserted at the beginning
--- of the virtual text.
--- * prefix: (string) Prepend diagnostic message with prefix.
+--- * suffix: (string or function) Append diagnostic message with suffix.
+--- If a function, it must have the signature (diagnostic) ->
+--- string, where {diagnostic} is of type |diagnostic-structure|.
+--- This can be used to render an LSP diagnostic error code.
--- * 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:
---- <pre>
---- function(diagnostic)
---- if diagnostic.severity == vim.diagnostic.severity.ERROR then
---- return string.format("E: %s", diagnostic.message)
+--- <pre>lua
+--- function(diagnostic)
+--- if diagnostic.severity == vim.diagnostic.severity.ERROR then
+--- return string.format("E: %s", diagnostic.message)
+--- end
+--- return diagnostic.message
--- end
---- return diagnostic.message
---- end
--- </pre>
--- - signs: (default true) Use signs for diagnostics. Options:
--- * severity: Only show signs for diagnostics matching the given severity
@@ -673,13 +657,13 @@ function M.config(opts, namespace)
if namespace then
for bufnr, v in pairs(diagnostic_cache) do
- if vim.api.nvim_buf_is_loaded(bufnr) and v[namespace] then
+ if api.nvim_buf_is_loaded(bufnr) and v[namespace] then
M.show(namespace, bufnr)
end
end
else
for bufnr, v in pairs(diagnostic_cache) do
- if vim.api.nvim_buf_is_loaded(bufnr) then
+ if api.nvim_buf_is_loaded(bufnr) then
for ns in pairs(v) do
M.show(ns, bufnr)
end
@@ -714,13 +698,14 @@ function M.set(namespace, bufnr, diagnostics, opts)
set_diagnostic_cache(namespace, bufnr, diagnostics)
end
- if vim.api.nvim_buf_is_loaded(bufnr) then
+ if api.nvim_buf_is_loaded(bufnr) then
M.show(namespace, bufnr, nil, opts)
end
- vim.api.nvim_exec_autocmds('DiagnosticChanged', {
+ api.nvim_exec_autocmds('DiagnosticChanged', {
modeline = false,
buffer = bufnr,
+ data = { diagnostics = diagnostics },
})
end
@@ -732,7 +717,7 @@ function M.get_namespace(namespace)
vim.validate({ namespace = { namespace, 'n' } })
if not all_namespaces[namespace] then
local name
- for k, v in pairs(vim.api.nvim_get_namespaces()) do
+ for k, v in pairs(api.nvim_get_namespaces()) do
if namespace == v then
name = k
break
@@ -757,6 +742,18 @@ function M.get_namespaces()
return vim.deepcopy(all_namespaces)
end
+---@class Diagnostic
+---@field buffer number
+---@field lnum number 0-indexed
+---@field end_lnum nil|number 0-indexed
+---@field col number 0-indexed
+---@field end_col nil|number 0-indexed
+---@field severity DiagnosticSeverity
+---@field message string
+---@field source nil|string
+---@field code nil|string
+---@field user_data nil|any arbitrary data plugins can add
+
--- Get current diagnostics.
---
---@param bufnr number|nil Buffer number to get diagnostics from. Use 0 for
@@ -765,7 +762,7 @@ end
--- - namespace: (number) Limit diagnostics to the given namespace.
--- - lnum: (number) Limit diagnostics to the given line number.
--- - severity: See |diagnostic-severity|.
----@return table A list of diagnostic items |diagnostic-structure|.
+---@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|.
function M.get(bufnr, opts)
vim.validate({
bufnr = { bufnr, 'n', true },
@@ -777,22 +774,23 @@ end
--- Get the previous diagnostic closest to the cursor position.
---
----@param opts table See |vim.diagnostic.goto_next()|
----@return table Previous diagnostic
+---@param opts nil|table See |vim.diagnostic.goto_next()|
+---@return Diagnostic|nil Previous diagnostic
function M.get_prev(opts)
opts = opts or {}
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
- local bufnr = vim.api.nvim_win_get_buf(win_id)
- local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
+ local win_id = opts.win_id or api.nvim_get_current_win()
+ local bufnr = api.nvim_win_get_buf(win_id)
+ local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id)
return next_diagnostic(cursor_position, false, bufnr, opts, opts.namespace)
end
--- Return the position of the previous diagnostic in the current buffer.
---
----@param opts table See |vim.diagnostic.goto_next()|
----@return table Previous diagnostic position as a (row, col) tuple.
+---@param opts table|nil See |vim.diagnostic.goto_next()|
+---@return table|false Previous diagnostic position as a (row, col) tuple or false if there is no
+--- prior diagnostic
function M.get_prev_pos(opts)
local prev = M.get_prev(opts)
if not prev then
@@ -803,29 +801,30 @@ function M.get_prev_pos(opts)
end
--- Move to the previous diagnostic in the current buffer.
----@param opts table See |vim.diagnostic.goto_next()|
+---@param opts table|nil See |vim.diagnostic.goto_next()|
function M.goto_prev(opts)
return diagnostic_move_pos(opts, M.get_prev_pos(opts))
end
--- Get the next diagnostic closest to the cursor position.
---
----@param opts table See |vim.diagnostic.goto_next()|
----@return table Next diagnostic
+---@param opts table|nil See |vim.diagnostic.goto_next()|
+---@return Diagnostic|nil Next diagnostic
function M.get_next(opts)
opts = opts or {}
- local win_id = opts.win_id or vim.api.nvim_get_current_win()
- local bufnr = vim.api.nvim_win_get_buf(win_id)
- local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id)
+ local win_id = opts.win_id or api.nvim_get_current_win()
+ local bufnr = api.nvim_win_get_buf(win_id)
+ local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id)
return next_diagnostic(cursor_position, true, bufnr, opts, opts.namespace)
end
--- Return the position of the next diagnostic in the current buffer.
---
----@param opts table See |vim.diagnostic.goto_next()|
----@return table Next diagnostic position as a (row, col) tuple.
+---@param opts table|nil See |vim.diagnostic.goto_next()|
+---@return table|false Next diagnostic position as a (row, col) tuple or false if no next
+--- diagnostic.
function M.get_next_pos(opts)
local next = M.get_next(opts)
if not next then
@@ -909,7 +908,7 @@ M.handlers.signs = {
end,
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)
- if ns.user_data.sign_group then
+ if ns.user_data.sign_group and api.nvim_buf_is_valid(bufnr) then
vim.fn.sign_unplace(ns.user_data.sign_group, { buffer = bufnr })
end
end,
@@ -937,7 +936,7 @@ M.handlers.underline = {
local ns = M.get_namespace(namespace)
if not ns.user_data.underline_ns then
- ns.user_data.underline_ns = vim.api.nvim_create_namespace('')
+ ns.user_data.underline_ns = api.nvim_create_namespace('')
end
local underline_ns = ns.user_data.underline_ns
@@ -964,7 +963,9 @@ M.handlers.underline = {
local ns = M.get_namespace(namespace)
if ns.user_data.underline_ns then
diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {}
- vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
+ if api.nvim_buf_is_valid(bufnr) then
+ api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
+ end
end
end,
}
@@ -1003,7 +1004,7 @@ M.handlers.virtual_text = {
local ns = M.get_namespace(namespace)
if not ns.user_data.virt_text_ns then
- ns.user_data.virt_text_ns = vim.api.nvim_create_namespace('')
+ ns.user_data.virt_text_ns = api.nvim_create_namespace('')
end
local virt_text_ns = ns.user_data.virt_text_ns
@@ -1015,7 +1016,7 @@ M.handlers.virtual_text = {
local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts.virtual_text)
if virt_texts then
- vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
+ api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
hl_mode = 'combine',
virt_text = virt_texts,
})
@@ -1027,7 +1028,9 @@ M.handlers.virtual_text = {
local ns = M.get_namespace(namespace)
if ns.user_data.virt_text_ns then
diagnostic_cache_extmarks[bufnr][ns.user_data.virt_text_ns] = {}
- vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1)
+ if api.nvim_buf_is_valid(bufnr) then
+ api.nvim_buf_clear_namespace(bufnr, ns.user_data.virt_text_ns, 0, -1)
+ end
end
end,
}
@@ -1045,6 +1048,7 @@ function M._get_virt_text_chunks(line_diags, opts)
opts = opts or {}
local prefix = opts.prefix or '■'
+ local suffix = opts.suffix or ''
local spacing = opts.spacing or 4
-- Create a little more space between virtual text and contents
@@ -1058,8 +1062,11 @@ function M._get_virt_text_chunks(line_diags, opts)
-- TODO(tjdevries): Allow different servers to be shown first somehow?
-- TODO(tjdevries): Display server name associated with these?
if last.message then
+ if type(suffix) == 'function' then
+ suffix = suffix(last) or ''
+ end
table.insert(virt_texts, {
- string.format('%s %s', prefix, last.message:gsub('\r', ''):gsub('\n', ' ')),
+ string.format('%s %s%s', prefix, last.message:gsub('\r', ''):gsub('\n', ' '), suffix),
virtual_text_highlight_map[last.severity],
})
@@ -1099,6 +1106,27 @@ function M.hide(namespace, bufnr)
end
end
+--- Check whether diagnostics are disabled in a given buffer.
+---
+---@param bufnr number|nil Buffer number, or 0 for current buffer.
+---@param namespace number|nil Diagnostic namespace. When omitted, checks if
+--- all diagnostics are disabled in {bufnr}.
+--- Otherwise, only checks if diagnostics from
+--- {namespace} are disabled.
+---@return boolean
+function M.is_disabled(bufnr, namespace)
+ bufnr = get_bufnr(bufnr)
+ if namespace and M.get_namespace(namespace).disabled then
+ return true
+ end
+
+ if type(diagnostic_disabled[bufnr]) == 'table' then
+ return diagnostic_disabled[bufnr][namespace]
+ end
+
+ return diagnostic_disabled[bufnr] ~= nil
+end
+
--- Display diagnostics for the given namespace and buffer.
---
---@param namespace number|nil Diagnostic namespace. When omitted, show
@@ -1142,7 +1170,7 @@ function M.show(namespace, bufnr, diagnostics, opts)
return
end
- if is_disabled(namespace, bufnr) then
+ if M.is_disabled(bufnr, namespace) then
return
end
@@ -1159,7 +1187,7 @@ function M.show(namespace, bufnr, diagnostics, opts)
if opts.update_in_insert then
clear_scheduled_display(namespace, bufnr)
else
- local mode = vim.api.nvim_get_mode()
+ local mode = api.nvim_get_mode()
if string.sub(mode.mode, 1, 1) == 'i' then
schedule_display(namespace, bufnr, opts)
return
@@ -1226,7 +1254,9 @@ end
--- string, it is prepended to each diagnostic in the window with no
--- highlight.
--- Overrides the setting from |vim.diagnostic.config()|.
----@return tuple ({float_bufnr}, {win_id})
+--- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of
+--- prepending it. Overrides the setting from |vim.diagnostic.config()|.
+---@return number|nil, number|nil: ({float_bufnr}, {win_id})
function M.open_float(opts, ...)
-- Support old (bufnr, opts) signature
local bufnr
@@ -1257,7 +1287,7 @@ function M.open_float(opts, ...)
local lnum, col
if scope == 'line' or scope == 'cursor' then
if not opts.pos then
- local pos = vim.api.nvim_win_get_cursor(0)
+ local pos = api.nvim_win_get_cursor(0)
lnum = pos[1] - 1
col = pos[2]
elseif type(opts.pos) == 'number' then
@@ -1279,7 +1309,7 @@ function M.open_float(opts, ...)
end, diagnostics)
elseif scope == 'cursor' then
-- LSP servers can send diagnostics with `end_col` past the length of the line
- local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
+ local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
diagnostics = vim.tbl_filter(function(d)
return d.lnum == lnum
and math.min(d.col, line_length - 1) <= col
@@ -1311,9 +1341,7 @@ function M.open_float(opts, ...)
vim.validate({
header = {
header,
- function(v)
- return type(v) == 'string' or type(v) == 'table'
- end,
+ { 'string', 'table' },
"'string' or 'table'",
},
})
@@ -1321,11 +1349,11 @@ function M.open_float(opts, ...)
-- Don't insert any lines for an empty string
if string.len(if_nil(header[1], '')) > 0 then
table.insert(lines, header[1])
- table.insert(highlights, { 0, header[2] or 'Bold' })
+ table.insert(highlights, { hlname = header[2] or 'Bold' })
end
elseif #header > 0 then
table.insert(lines, header)
- table.insert(highlights, { 0, 'Bold' })
+ table.insert(highlights, { hlname = 'Bold' })
end
end
@@ -1347,9 +1375,7 @@ function M.open_float(opts, ...)
vim.validate({
prefix = {
prefix_opt,
- function(v)
- return type(v) == 'string' or type(v) == 'table' or type(v) == 'function'
- end,
+ { 'string', 'table', 'function' },
"'string' or 'table' or 'function'",
},
})
@@ -1360,18 +1386,52 @@ function M.open_float(opts, ...)
end
end
+ local suffix_opt = if_nil(opts.suffix, function(diagnostic)
+ return diagnostic.code and string.format(' [%s]', diagnostic.code) or ''
+ end)
+
+ local suffix, suffix_hl_group
+ if suffix_opt then
+ vim.validate({
+ suffix = {
+ suffix_opt,
+ { 'string', 'table', 'function' },
+ "'string' or 'table' or 'function'",
+ },
+ })
+ if type(suffix_opt) == 'string' then
+ suffix, suffix_hl_group = suffix_opt, 'NormalFloat'
+ elseif type(suffix_opt) == 'table' then
+ suffix, suffix_hl_group = suffix_opt[1] or '', suffix_opt[2] or 'NormalFloat'
+ end
+ end
+
for i, diagnostic in ipairs(diagnostics) do
if prefix_opt and type(prefix_opt) == 'function' then
prefix, prefix_hl_group = prefix_opt(diagnostic, i, #diagnostics)
prefix, prefix_hl_group = prefix or '', prefix_hl_group or 'NormalFloat'
end
+ if suffix_opt and type(suffix_opt) == 'function' then
+ suffix, suffix_hl_group = suffix_opt(diagnostic, i, #diagnostics)
+ suffix, suffix_hl_group = suffix or '', suffix_hl_group or 'NormalFloat'
+ end
local hiname = floating_highlight_map[diagnostic.severity]
local message_lines = vim.split(diagnostic.message, '\n')
- table.insert(lines, prefix .. message_lines[1])
- table.insert(highlights, { #prefix, hiname, prefix_hl_group })
- for j = 2, #message_lines do
- table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
- table.insert(highlights, { 0, hiname })
+ for j = 1, #message_lines do
+ local pre = j == 1 and prefix or string.rep(' ', #prefix)
+ local suf = j == #message_lines and suffix or ''
+ table.insert(lines, pre .. message_lines[j] .. suf)
+ table.insert(highlights, {
+ hlname = hiname,
+ prefix = {
+ length = j == 1 and #prefix or 0,
+ hlname = prefix_hl_group,
+ },
+ suffix = {
+ length = j == #message_lines and #suffix or 0,
+ hlname = suffix_hl_group,
+ },
+ })
end
end
@@ -1380,12 +1440,17 @@ function M.open_float(opts, ...)
opts.focus_id = scope
end
local float_bufnr, winnr = require('vim.lsp.util').open_floating_preview(lines, 'plaintext', opts)
- for i, hi in ipairs(highlights) do
- local prefixlen, hiname, prefix_hiname = unpack(hi)
- if prefix_hiname then
- vim.api.nvim_buf_add_highlight(float_bufnr, -1, prefix_hiname, i - 1, 0, prefixlen)
+ for i, hl in ipairs(highlights) do
+ local line = lines[i]
+ local prefix_len = hl.prefix and hl.prefix.length or 0
+ local suffix_len = hl.suffix and hl.suffix.length or 0
+ if prefix_len > 0 then
+ api.nvim_buf_add_highlight(float_bufnr, -1, hl.prefix.hlname, i - 1, 0, prefix_len)
+ end
+ api.nvim_buf_add_highlight(float_bufnr, -1, hl.hlname, i - 1, prefix_len, #line - suffix_len)
+ if suffix_len > 0 then
+ api.nvim_buf_add_highlight(float_bufnr, -1, hl.suffix.hlname, i - 1, #line - suffix_len, -1)
end
- vim.api.nvim_buf_add_highlight(float_bufnr, -1, hiname, i - 1, prefixlen, -1)
end
return float_bufnr, winnr
@@ -1416,10 +1481,15 @@ function M.reset(namespace, bufnr)
M.hide(iter_namespace, iter_bufnr)
end
- vim.api.nvim_exec_autocmds('DiagnosticChanged', {
- modeline = false,
- buffer = iter_bufnr,
- })
+ if api.nvim_buf_is_valid(iter_bufnr) then
+ api.nvim_exec_autocmds('DiagnosticChanged', {
+ modeline = false,
+ buffer = iter_bufnr,
+ data = { diagnostics = {} },
+ })
+ else
+ diagnostic_cache[iter_bufnr] = nil
+ end
end
end
@@ -1522,11 +1592,11 @@ end
---
--- This can be parsed into a diagnostic |diagnostic-structure|
--- with:
---- <pre>
---- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
---- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
---- local groups = { "severity", "lnum", "col", "message" }
---- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
+--- <pre>lua
+--- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
+--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
+--- local groups = { "severity", "lnum", "col", "message" }
+--- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
--- </pre>
---
---@param str string String to parse diagnostics from.
@@ -1538,7 +1608,7 @@ end
---@param defaults table|nil Table of default values for any fields not listed in {groups}.
--- When omitted, numeric values default to 0 and "severity" defaults to
--- ERROR.
----@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}.
+---@return Diagnostic|nil: |diagnostic-structure| or `nil` if {pat} fails to match {str}.
function M.match(str, pat, groups, severity_map, defaults)
vim.validate({
str = { str, 's' },
@@ -1585,7 +1655,7 @@ local errlist_type_map = {
--- passed to |setqflist()| or |setloclist()|.
---
---@param diagnostics table List of diagnostics |diagnostic-structure|.
----@return array of quickfix list items |setqflist-what|
+---@return table[] of quickfix list items |setqflist-what|
function M.toqflist(diagnostics)
vim.validate({
diagnostics = {
@@ -1610,7 +1680,11 @@ function M.toqflist(diagnostics)
end
table.sort(list, function(a, b)
if a.bufnr == b.bufnr then
- return a.lnum < b.lnum
+ if a.lnum == b.lnum then
+ return a.col < b.col
+ else
+ return a.lnum < b.lnum
+ end
else
return a.bufnr < b.bufnr
end
@@ -1622,7 +1696,7 @@ end
---
---@param list table A list of quickfix items from |getqflist()| or
--- |getloclist()|.
----@return array of diagnostics |diagnostic-structure|
+---@return Diagnostic[] array of |diagnostic-structure|
function M.fromqflist(list)
vim.validate({
list = {