refactor(diagnostic)!: replace 'show_*' functions with 'open_float' (#16057)

'show_line_diagnostics()' and 'show_position_diagnostics()' are
almost identical; they differ only in the fact that the latter also
accepts a column to form a full position, rather than just a line. This
is not enough to justify two separate interfaces for this common
functionality.

Renaming this to simply 'show_diagnostics()' is one step forward, but
that is also not a good name as the '_diagnostics()' suffix is
redundant. However, we cannot name it simply 'show()' since that
function already exists with entirely different semantics.

Instead, combine these two into a single 'open_float()' function that
handles all of the cases of showing diagnostics in a floating window.
Also add a "float" key to 'vim.diagnostic.config()' to provide global
values of configuration options that can be overridden ephemerally.
This makes the float API consistent with the rest of the diagnostic API.

BREAKING CHANGE
This commit is contained in:
Gregory Anders 2021-10-19 11:45:51 -06:00 committed by GitHub
parent aa4f0879e3
commit 064411ea7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 331 additions and 229 deletions

View File

@ -262,12 +262,12 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
For example, if a user enables virtual text globally with > For example, if a user enables virtual text globally with >
vim.diagnostic.config({virt_text = true}) vim.diagnostic.config({virtual_text = true})
< <
and a diagnostic producer sets diagnostics with > and a diagnostic producer sets diagnostics with >
vim.diagnostic.set(ns, 0, diagnostics, {virt_text = false}) vim.diagnostic.set(ns, 0, diagnostics, {virtual_text = false})
< <
then virtual text will not be enabled for those diagnostics. then virtual text will not be enabled for those diagnostics.
@ -302,7 +302,7 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
• format: (function) A function that takes • format: (function) A function that takes
a diagnostic as input and returns a a diagnostic as input and returns a
string. The return value is the text used string. The return value is the text used
to display the diagnostic. Example:> to display the diagnostic. Example: >
function(diagnostic) function(diagnostic)
if diagnostic.severity == vim.diagnostic.severity.ERROR then if diagnostic.severity == vim.diagnostic.severity.ERROR then
@ -324,6 +324,18 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
Otherwise, all signs use the same Otherwise, all signs use the same
priority. priority.
• float: Options for floating windows:
• severity: See |diagnostic-severity|.
• show_header: (boolean, default true) Show
"Diagnostics:" header
• source: (string) Include the diagnostic
source in the message. One of "always" or
"if_many".
• 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.
• update_in_insert: (default false) Update • update_in_insert: (default false) Update
diagnostics in Insert mode (if false, diagnostics in Insert mode (if false,
diagnostics are updated on InsertLeave) diagnostics are updated on InsertLeave)
@ -442,12 +454,10 @@ goto_next({opts}) *vim.diagnostic.goto_next()*
• wrap: (boolean, default true) Whether to loop • wrap: (boolean, default true) Whether to loop
around file or not. Similar to 'wrapscan'. around file or not. Similar to 'wrapscan'.
• severity: See |diagnostic-severity|. • severity: See |diagnostic-severity|.
• enable_popup: (boolean, default true) Call • float: (boolean or table, default true) If
|vim.diagnostic.show_line_diagnostics()| on "true", call |vim.diagnostic.open_float()| after
jump. moving. If a table, pass the table as the {opts}
• popup_opts: (table) Table to pass as {opts} parameter to |vim.diagnostic.open_float()|.
parameter to
|vim.diagnostic.show_line_diagnostics()|
• win_id: (number, default 0) Window ID • win_id: (number, default 0) Window ID
goto_prev({opts}) *vim.diagnostic.goto_prev()* goto_prev({opts}) *vim.diagnostic.goto_prev()*
@ -508,6 +518,46 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults})
diagnostic |diagnostic-structure| or `nil` if {pat} fails diagnostic |diagnostic-structure| or `nil` if {pat} fails
to match {str}. to match {str}.
open_float({bufnr}, {opts}) *vim.diagnostic.open_float()*
Show diagnostics in a floating window.
Parameters: ~
{bufnr} number|nil Buffer number. Defaults to the current
buffer.
{opts} table|nil Configuration table with the same keys
as |vim.lsp.util.open_floating_preview()| in
addition to the following:
• namespace: (number) Limit diagnostics to the
given namespace
• scope: (string, default "buffer") Show
diagnostics from the whole buffer ("buffer"),
the current cursor line ("line"), or the
current cursor position ("cursor").
• pos: (number or table) If {scope} is "line" or
"cursor", use this position rather than the
cursor position. If a number, interpreted as a
line number; otherwise, a (row, col) tuple.
• severity_sort: (default false) Sort diagnostics
by severity. Overrides the setting from
|vim.diagnostic.config()|.
• severity: See |diagnostic-severity|. Overrides
the setting from |vim.diagnostic.config()|.
• show_header: (boolean, default true) Show
"Diagnostics:" header. 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()|.
• 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()|.
Return: ~
tuple ({float_bufnr}, {win_id})
reset({namespace}, {bufnr}) *vim.diagnostic.reset()* reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
Remove all diagnostics from the given namespace. Remove all diagnostics from the given namespace.
@ -581,51 +631,6 @@ show({namespace}, {bufnr}, {diagnostics}, {opts})
{opts} table|nil Display options. See {opts} table|nil Display options. See
|vim.diagnostic.config()|. |vim.diagnostic.config()|.
*vim.diagnostic.show_line_diagnostics()*
show_line_diagnostics({opts}, {bufnr}, {lnum})
Open a floating window with the diagnostics from the given
line.
Parameters: ~
{opts} table Configuration table. See
|vim.diagnostic.show_position_diagnostics()|.
{bufnr} number|nil Buffer number. Defaults to the current
buffer.
{lnum} number|nil Line number. Defaults to line number
of cursor.
Return: ~
tuple ({popup_bufnr}, {win_id})
*vim.diagnostic.show_position_diagnostics()*
show_position_diagnostics({opts}, {bufnr}, {position})
Open a floating window with the diagnostics at the given
position.
Parameters: ~
{opts} table|nil Configuration table with the same
keys as |vim.lsp.util.open_floating_preview()|
in addition to the following:
• namespace: (number) Limit diagnostics to the
given namespace
• severity: See |diagnostic-severity|.
• show_header: (boolean, default true) Show
"Diagnostics:" header
• source: (string) Include the diagnostic
source in the message. One of "always" or
"if_many".
• 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.
{bufnr} number|nil Buffer number. Defaults to the
current buffer.
{position} table|nil The (0,0)-indexed position. Defaults
to the current cursor position.
Return: ~
tuple ({popup_bufnr}, {win_id})
toqflist({diagnostics}) *vim.diagnostic.toqflist()* toqflist({diagnostics}) *vim.diagnostic.toqflist()*
Convert a list of diagnostics to a list of quickfix items that Convert a list of diagnostics to a list of quickfix items that
can be passed to |setqflist()| or |setloclist()|. can be passed to |setqflist()| or |setloclist()|.

View File

@ -19,6 +19,7 @@ local global_diagnostic_options = {
signs = true, signs = true,
underline = true, underline = true,
virtual_text = true, virtual_text = true,
float = true,
update_in_insert = false, update_in_insert = false,
severity_sort = false, severity_sort = false,
} }
@ -119,8 +120,8 @@ end
---@private ---@private
local function enabled_value(option, namespace) local function enabled_value(option, namespace)
local ns = get_namespace(namespace) local ns = namespace and get_namespace(namespace) or {}
if type(ns.opts[option]) == "table" then if ns.opts and type(ns.opts[option]) == "table" then
return ns.opts[option] return ns.opts[option]
end end
@ -153,8 +154,9 @@ end
---@private ---@private
local function get_resolved_options(opts, namespace, bufnr) local function get_resolved_options(opts, namespace, bufnr)
local ns = get_namespace(namespace) local ns = namespace and get_namespace(namespace) or {}
local resolved = vim.tbl_extend('keep', opts or {}, ns.opts, global_diagnostic_options) -- Do not use tbl_deep_extend so that an empty table can be used to reset to default values
local resolved = vim.tbl_extend('keep', opts or {}, ns.opts or {}, global_diagnostic_options)
for k in pairs(global_diagnostic_options) do for k in pairs(global_diagnostic_options) do
if resolved[k] ~= nil then if resolved[k] ~= nil then
resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr) resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr)
@ -354,19 +356,15 @@ local function schedule_display(namespace, bufnr, args)
local key = make_augroup_key(namespace, bufnr) local key = make_augroup_key(namespace, bufnr)
if not registered_autocmds[key] then if not registered_autocmds[key] then
vim.cmd(string.format("augroup %s", key)) vim.cmd(string.format([[augroup %s
vim.cmd(" au!") au!
vim.cmd( autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)
string.format( augroup END]],
[[autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)]], key,
table.concat(insert_leave_auto_cmds, ","), table.concat(insert_leave_auto_cmds, ","),
bufnr, bufnr,
namespace, namespace,
bufnr bufnr))
)
)
vim.cmd("augroup END")
registered_autocmds[key] = true registered_autocmds[key] = true
end end
end end
@ -376,76 +374,13 @@ local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr) local key = make_augroup_key(namespace, bufnr)
if registered_autocmds[key] then if registered_autocmds[key] then
vim.cmd(string.format("augroup %s", key)) vim.cmd(string.format([[augroup %s
vim.cmd(" au!") au!
vim.cmd("augroup END") augroup END]], key))
registered_autocmds[key] = nil registered_autocmds[key] = nil
end end
end end
---@private
--- Open a floating window with the provided diagnostics
---@param opts table Configuration table
--- - show_header (boolean, default true): Show "Diagnostics:" header
--- - all opts for |vim.util.open_floating_preview()| can be used here
---@param diagnostics table: The diagnostics to display
---@return table {popup_bufnr, win_id}
local function show_diagnostics(opts, diagnostics)
if not diagnostics or vim.tbl_isempty(diagnostics) then
return
end
local lines = {}
local highlights = {}
local show_header = vim.F.if_nil(opts.show_header, true)
if show_header then
table.insert(lines, "Diagnostics:")
table.insert(highlights, {0, "Bold"})
end
if opts.format then
diagnostics = reformat_diagnostics(opts.format, diagnostics)
end
if opts.source then
diagnostics = prefix_source(opts.source, diagnostics)
end
-- Use global setting for severity_sort since 'show_diagnostics' is namespace
-- independent
local severity_sort = global_diagnostic_options.severity_sort
if severity_sort then
if type(severity_sort) == "table" and severity_sort.reverse then
table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
else
table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
end
end
for i, diagnostic in ipairs(diagnostics) do
local prefix = string.format("%d. ", i)
local hiname = floating_highlight_map[diagnostic.severity]
assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
local message_lines = vim.split(diagnostic.message, '\n', true)
table.insert(lines, prefix..message_lines[1])
table.insert(highlights, {#prefix, hiname})
for j = 2, #message_lines do
table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
table.insert(highlights, {0, hiname})
end
end
local popup_bufnr, winnr = require('vim.lsp.util').open_floating_preview(lines, 'plaintext', opts)
for i, hi in ipairs(highlights) do
local prefixlen, hiname = unpack(hi)
-- Start highlight after the prefix
vim.api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1)
end
return popup_bufnr, winnr
end
---@private ---@private
local function set_list(loclist, opts) local function set_list(loclist, opts)
opts = opts or {} opts = opts or {}
@ -469,6 +404,7 @@ local function set_list(loclist, opts)
end end
---@private ---@private
--- To (slightly) improve performance, modifies diagnostics in place.
local function clamp_line_numbers(bufnr, diagnostics) local function clamp_line_numbers(bufnr, diagnostics)
local buf_line_count = vim.api.nvim_buf_line_count(bufnr) local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
if buf_line_count == 0 then if buf_line_count == 0 then
@ -526,7 +462,7 @@ end
local function diagnostic_move_pos(opts, pos) local function diagnostic_move_pos(opts, pos)
opts = opts or {} opts = opts or {}
local enable_popup = vim.F.if_nil(opts.enable_popup, true) 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 vim.api.nvim_get_current_win()
if not pos then if not pos then
@ -539,10 +475,13 @@ local function diagnostic_move_pos(opts, pos)
vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]})
if enable_popup then if float then
-- This is a bit weird... I'm surprised that we need to wait til the next tick to do this. local float_opts = type(float) == "table" and float or {}
vim.schedule(function() vim.schedule(function()
M.show_position_diagnostics(opts.popup_opts, vim.api.nvim_win_get_buf(win_id)) M.open_float(
vim.api.nvim_win_get_buf(win_id),
vim.tbl_extend("keep", float_opts, {scope="cursor"})
)
end) end)
end end
end end
@ -561,12 +500,12 @@ end
--- ---
--- For example, if a user enables virtual text globally with --- For example, if a user enables virtual text globally with
--- <pre> --- <pre>
--- vim.diagnostic.config({virt_text = true}) --- vim.diagnostic.config({virtual_text = true})
--- </pre> --- </pre>
--- ---
--- and a diagnostic producer sets diagnostics with --- and a diagnostic producer sets diagnostics with
--- <pre> --- <pre>
--- vim.diagnostic.set(ns, 0, diagnostics, {virt_text = false}) --- vim.diagnostic.set(ns, 0, diagnostics, {virtual_text = false})
--- </pre> --- </pre>
--- ---
--- then virtual text will not be enabled for those diagnostics. --- then virtual text will not be enabled for those diagnostics.
@ -603,6 +542,13 @@ end
--- * priority: (number, default 10) Base priority to use for signs. When --- * priority: (number, default 10) Base priority to use for signs. When
--- {severity_sort} is used, the priority of a sign is adjusted based on --- {severity_sort} is used, the priority of a sign is adjusted based on
--- its severity. Otherwise, all signs use the same priority. --- its severity. Otherwise, all signs use the same priority.
--- - float: Options for floating windows:
--- * severity: See |diagnostic-severity|.
--- * show_header: (boolean, default true) Show "Diagnostics:" header
--- * source: (string) Include the diagnostic source in
--- the message. One of "always" or "if_many".
--- * 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.
--- - update_in_insert: (default false) Update diagnostics in Insert mode (if false, --- - update_in_insert: (default false) Update diagnostics in Insert mode (if false,
--- diagnostics are updated on InsertLeave) --- diagnostics are updated on InsertLeave)
--- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in --- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in
@ -825,10 +771,9 @@ end
--- |nvim_win_get_cursor()|. Defaults to the current cursor position. --- |nvim_win_get_cursor()|. Defaults to the current cursor position.
--- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'. --- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'.
--- - severity: See |diagnostic-severity|. --- - severity: See |diagnostic-severity|.
--- - enable_popup: (boolean, default true) Call |vim.diagnostic.show_line_diagnostics()| --- - float: (boolean or table, default true) If "true", call |vim.diagnostic.open_float()|
--- on jump. --- after moving. If a table, pass the table as the {opts} parameter to
--- - popup_opts: (table) Table to pass as {opts} parameter to --- |vim.diagnostic.open_float()|.
--- |vim.diagnostic.show_line_diagnostics()|
--- - win_id: (number, default 0) Window ID --- - win_id: (number, default 0) Window ID
function M.goto_next(opts) function M.goto_next(opts)
return diagnostic_move_pos( return diagnostic_move_pos(
@ -1144,68 +1089,128 @@ function M.show(namespace, bufnr, diagnostics, opts)
save_extmarks(namespace, bufnr) save_extmarks(namespace, bufnr)
end end
--- Open a floating window with the diagnostics at the given position. --- Show diagnostics in a floating window.
--- ---
---@param bufnr number|nil Buffer number. Defaults to the current buffer.
---@param opts table|nil Configuration table with the same keys as ---@param opts table|nil Configuration table with the same keys as
--- |vim.lsp.util.open_floating_preview()| in addition to the following: --- |vim.lsp.util.open_floating_preview()| in addition to the following:
--- - namespace: (number) Limit diagnostics to the given namespace --- - namespace: (number) Limit diagnostics to the given namespace
--- - severity: See |diagnostic-severity|. --- - scope: (string, default "buffer") Show diagnostics from the whole buffer ("buffer"),
--- - show_header: (boolean, default true) Show "Diagnostics:" header --- the current cursor line ("line"), or the current cursor position ("cursor").
--- - source: (string) Include the diagnostic source in --- - pos: (number or table) If {scope} is "line" or "cursor", use this position rather
--- the message. One of "always" or "if_many". --- than the cursor position. If a number, interpreted as a line number;
--- otherwise, a (row, col) tuple.
--- - severity_sort: (default false) Sort diagnostics by severity. Overrides the setting
--- from |vim.diagnostic.config()|.
--- - severity: See |diagnostic-severity|. Overrides the setting from
--- |vim.diagnostic.config()|.
--- - show_header: (boolean, default true) Show "Diagnostics:" header. 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()|.
--- - format: (function) A function that takes a diagnostic as input and returns a --- - 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. --- string. The return value is the text used to display the diagnostic.
---@param bufnr number|nil Buffer number. Defaults to the current buffer. --- Overrides the setting from |vim.diagnostic.config()|.
---@param position table|nil The (0,0)-indexed position. Defaults to the current cursor position. ---@return tuple ({float_bufnr}, {win_id})
---@return tuple ({popup_bufnr}, {win_id}) function M.open_float(bufnr, opts)
function M.show_position_diagnostics(opts, bufnr, position)
vim.validate { vim.validate {
opts = { opts, 't', true },
bufnr = { bufnr, 'n', true }, bufnr = { bufnr, 'n', true },
position = { position, 't', true }, opts = { opts, 't', true },
} }
opts = opts or {} opts = opts or {}
opts.focus_id = "position_diagnostics"
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
if not position then local scope = opts.scope or "buffer"
local curr_position = vim.api.nvim_win_get_cursor(0) local lnum, col
curr_position[1] = curr_position[1] - 1 if scope == "line" or scope == "cursor" then
position = curr_position if not opts.pos then
local pos = vim.api.nvim_win_get_cursor(0)
lnum = pos[1] - 1
col = pos[2]
elseif type(opts.pos) == "number" then
lnum = opts.pos
elseif type(opts.pos) == "table" then
lnum, col = unpack(opts.pos)
else
error("Invalid value for option 'pos'")
end end
local match_position_predicate = function(diag) elseif scope ~= "buffer" then
return position[1] == diag.lnum and error("Invalid value for option 'scope'")
position[2] >= diag.col and
(position[2] <= diag.end_col or position[1] < diag.end_lnum)
end end
local diagnostics = M.get(bufnr, opts) local diagnostics = M.get(bufnr, opts)
clamp_line_numbers(bufnr, diagnostics) clamp_line_numbers(bufnr, diagnostics)
local position_diagnostics = vim.tbl_filter(match_position_predicate, diagnostics)
return show_diagnostics(opts, position_diagnostics)
end
--- Open a floating window with the diagnostics from the given line. if scope == "line" then
--- diagnostics = vim.tbl_filter(function(d)
---@param opts table Configuration table. See |vim.diagnostic.show_position_diagnostics()|. return d.lnum == lnum
---@param bufnr number|nil Buffer number. Defaults to the current buffer. end, diagnostics)
---@param lnum number|nil Line number. Defaults to line number of cursor. elseif scope == "cursor" then
---@return tuple ({popup_bufnr}, {win_id}) diagnostics = vim.tbl_filter(function(d)
function M.show_line_diagnostics(opts, bufnr, lnum) return d.lnum == lnum and d.col <= col and (d.end_col >= col or d.end_lnum > lnum)
vim.validate { end, diagnostics)
opts = { opts, 't', true }, end
bufnr = { bufnr, 'n', true },
lnum = { lnum, 'n', true },
}
opts = opts or {} if vim.tbl_isempty(diagnostics) then
opts.focus_id = "line_diagnostics" return
bufnr = get_bufnr(bufnr) end
local diagnostics = M.get(bufnr, opts)
clamp_line_numbers(bufnr, diagnostics) local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
lnum = lnum or (vim.api.nvim_win_get_cursor(0)[1] - 1) if severity_sort then
local line_diagnostics = diagnostic_lines(diagnostics)[lnum] if type(severity_sort) == "table" and severity_sort.reverse then
return show_diagnostics(opts, line_diagnostics) table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
else
table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
end
end
do
-- Resolve options with user settings from vim.diagnostic.config
-- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float`
-- does not have a dedicated table for configuration options; instead, the options are mixed in
-- with its `opts` table which also includes "keyword" parameters. So we create a dedicated
-- options table that inherits missing keys from the global configuration before resolving.
local t = global_diagnostic_options.float
local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {})
opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
end
local lines = {}
local highlights = {}
local show_header = vim.F.if_nil(opts.show_header, true)
if show_header then
table.insert(lines, "Diagnostics:")
table.insert(highlights, {0, "Bold"})
end
if opts.format then
diagnostics = reformat_diagnostics(opts.format, diagnostics)
end
if opts.source then
diagnostics = prefix_source(opts.source, diagnostics)
end
for i, diagnostic in ipairs(diagnostics) do
local prefix = string.format("%d. ", i)
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})
for j = 2, #message_lines do
table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
table.insert(highlights, {0, hiname})
end
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 = unpack(hi)
-- Start highlight after the prefix
vim.api.nvim_buf_add_highlight(float_bufnr, -1, hiname, i-1, prefixlen, -1)
end
return float_bufnr, winnr
end end
--- Remove all diagnostics from the given namespace. --- Remove all diagnostics from the given namespace.

View File

@ -551,14 +551,15 @@ end
---@param position table|nil The (0,0)-indexed position ---@param position table|nil The (0,0)-indexed position
---@return table {popup_bufnr, win_id} ---@return table {popup_bufnr, win_id}
function M.show_position_diagnostics(opts, buf_nr, position) function M.show_position_diagnostics(opts, buf_nr, position)
if opts then opts = opts or {}
opts.where = "cursor"
opts.pos = position
if opts.severity then if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity) opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
end end
end return vim.diagnostic.open_float(buf_nr, opts)
return vim.diagnostic.show_position_diagnostics(opts, buf_nr, position)
end end
--- Open a floating window with the diagnostics from {line_nr} --- Open a floating window with the diagnostics from {line_nr}
@ -573,11 +574,13 @@ end
---@param client_id number|nil the client id ---@param client_id number|nil the client id
---@return table {popup_bufnr, win_id} ---@return table {popup_bufnr, win_id}
function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id) function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
if client_id then
opts = opts or {} opts = opts or {}
opts.where = "line"
opts.pos = line_nr
if client_id then
opts.namespace = M.get_namespace(client_id) opts.namespace = M.get_namespace(client_id)
end end
return vim.diagnostic.show_line_diagnostics(opts, buf_nr, line_nr) return vim.diagnostic.open_float(buf_nr, opts)
end end
--- Redraw diagnostics for the given buffer and client --- Redraw diagnostics for the given buffer and client

View File

@ -968,8 +968,87 @@ describe('vim.diagnostic', function()
end) end)
end) end)
describe('show_line_diagnostics()', function() describe('open_float()', function()
it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() it('can show diagnostics from the whole buffer', function()
eq({'1. Syntax error', '2. Some warning'}, exec_lua [[
local diagnostics = {
make_error("Syntax error", 0, 1, 0, 3),
make_warning("Some warning", 1, 1, 1, 3),
}
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header = false})
local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return lines
]])
end)
it('can show diagnostics from a single line', function()
-- Using cursor position
eq({'1. Some warning'}, exec_lua [[
local diagnostics = {
make_error("Syntax error", 0, 1, 0, 3),
make_warning("Some warning", 1, 1, 1, 3),
}
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
vim.api.nvim_win_set_cursor(0, {2, 1})
local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="line"})
local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return lines
]])
-- With specified position
eq({'1. Some warning'}, exec_lua [[
local diagnostics = {
make_error("Syntax error", 0, 1, 0, 3),
make_warning("Some warning", 1, 1, 1, 3),
}
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
vim.api.nvim_win_set_cursor(0, {1, 1})
local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="line", pos=1})
local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return lines
]])
end)
it('can show diagnostics from a specific position', function()
-- Using cursor position
eq({'1. Syntax error'}, exec_lua [[
local diagnostics = {
make_error("Syntax error", 1, 1, 1, 2),
make_warning("Some warning", 1, 3, 1, 4),
}
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
vim.api.nvim_win_set_cursor(0, {2, 2})
local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="cursor"})
local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return lines
]])
-- With specified position
eq({'1. Some warning'}, exec_lua [[
local diagnostics = {
make_error("Syntax error", 1, 1, 1, 2),
make_warning("Some warning", 1, 3, 1, 4),
}
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
vim.api.nvim_win_set_cursor(0, {1, 1})
local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="cursor", pos={1,3}})
local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return lines
]])
end)
it('creates floating window and returns float bufnr and winnr if current line contains diagnostics', function()
-- Two lines: -- Two lines:
-- Diagnostic: -- Diagnostic:
-- 1. <msg> -- 1. <msg>
@ -979,8 +1058,10 @@ describe('vim.diagnostic', function()
} }
vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {scope="line"})
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return #lines
]]) ]])
end) end)
@ -996,8 +1077,10 @@ describe('vim.diagnostic', function()
vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, buf_1_diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, buf_1_diagnostics)
vim.diagnostic.set(other_ns, other_bufnr, buf_2_diagnostics) vim.diagnostic.set(other_ns, other_bufnr, buf_2_diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() local float_bufnr, winnr = vim.diagnostic.open_float()
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return #lines
]]) ]])
end) end)
@ -1012,12 +1095,14 @@ describe('vim.diagnostic', function()
vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diagnostics)
vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diagnostics) vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({namespace = diagnostic_ns}) local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {namespace = diagnostic_ns})
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return #lines
]]) ]])
end) end)
it('creates floating window and returns popup bufnr and winnr without header, if requested', function() it('creates floating window and returns float bufnr and winnr without header, if requested', function()
-- One line (since no header): -- One line (since no header):
-- 1. <msg> -- 1. <msg>
eq(1, exec_lua [[ eq(1, exec_lua [[
@ -1026,8 +1111,10 @@ describe('vim.diagnostic', function()
} }
vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics {show_header = false} local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {show_header = false})
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return #lines
]]) ]])
end) end)
@ -1038,8 +1125,10 @@ describe('vim.diagnostic', function()
} }
vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({show_header = false}, diagnostic_bufnr, 5) local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {show_header = false, scope = "line", pos = 5})
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true)
return #lines
]]) ]])
end) end)
@ -1051,21 +1140,21 @@ describe('vim.diagnostic', function()
make_error("Syntax error", 0, 1, 0, 3, "source x"), make_error("Syntax error", 0, 1, 0, 3, "source x"),
} }
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {
show_header = false, show_header = false,
source = "if_many", source = "if_many",
} })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])
eq({"1. source x: Syntax error"}, exec_lua [[ eq({"1. source x: Syntax error"}, exec_lua [[
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {
show_header = false, show_header = false,
source = "always", source = "always",
} })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])
@ -1076,11 +1165,11 @@ describe('vim.diagnostic', function()
make_error("Another error", 0, 1, 0, 3, "source y"), make_error("Another error", 0, 1, 0, 3, "source y"),
} }
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics)
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {
show_header = false, show_header = false,
source = "if_many", source = "if_many",
} })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])
@ -1101,24 +1190,24 @@ describe('vim.diagnostic', function()
vim.diagnostic.config({severity_sort = false}) vim.diagnostic.config({severity_sort = false})
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { show_header = false } local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])
eq({"1. Syntax error", "2. Error", "3. Warning", "4. Info"}, exec_lua [[ eq({"1. Syntax error", "2. Error", "3. Warning", "4. Info"}, exec_lua [[
vim.diagnostic.config({severity_sort = true}) vim.diagnostic.config({severity_sort = true})
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { show_header = false } local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])
eq({"1. Info", "2. Warning", "3. Error", "4. Syntax error"}, exec_lua [[ eq({"1. Info", "2. Warning", "3. Error", "4. Syntax error"}, exec_lua [[
vim.diagnostic.config({severity_sort = { reverse = true } }) vim.diagnostic.config({severity_sort = { reverse = true } })
local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { show_header = false } local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false })
local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false)
vim.api.nvim_win_close(winnr, true) vim.api.nvim_win_close(winnr, true)
return lines return lines
]]) ]])