fix(vim.diagnostic): improve typing

Problem:

`vim.diagnostic.set()` doesn't actually accept a list of
`vim.Diagnostic` as internally `vim.diagnostic.set()` normalizes the
diagnostics and this normalization is assumed throughout the module.

Solution:

- Add a new type `vim.Diagnostic.Set` which is the input to `vim.diagnostic.set()`

- `col` is now an optional field and defaults to `0` to be consistent
  with `vim.diagnostic.match()`.

- Change `table.insert(t, x)` to `table[#table + 1] = x` for improved
  type checking.
This commit is contained in:
Lewis Russell
2025-06-02 13:44:00 +01:00
committed by Lewis Russell
parent 9641ad9369
commit 533cc0ab35
4 changed files with 171 additions and 95 deletions

View File

@@ -436,15 +436,15 @@ Lua module: vim.diagnostic *diagnostic-api*
0-based rows and columns). |api-indexing| 0-based rows and columns). |api-indexing|
Fields: ~ Fields: ~
• {bufnr}? (`integer`) Buffer number • {bufnr} (`integer`) Buffer number
• {lnum} (`integer`) The starting line of the diagnostic • {lnum} (`integer`) The starting line of the diagnostic
(0-indexed) (0-indexed)
• {end_lnum}? (`integer`) The final line of the diagnostic (0-indexed) • {end_lnum} (`integer`) The final line of the diagnostic (0-indexed)
• {col} (`integer`) The starting column of the diagnostic • {col} (`integer`) The starting column of the diagnostic
(0-indexed) (0-indexed)
• {end_col}? (`integer`) The final column of the diagnostic • {end_col} (`integer`) The final column of the diagnostic
(0-indexed) (0-indexed)
• {severity}? (`vim.diagnostic.Severity`) The severity of the • {severity} (`vim.diagnostic.Severity`) The severity of the
diagnostic |vim.diagnostic.severity| diagnostic |vim.diagnostic.severity|
• {message} (`string`) The diagnostic text • {message} (`string`) The diagnostic text
• {source}? (`string`) The source of the diagnostic • {source}? (`string`) The source of the diagnostic
@@ -452,6 +452,24 @@ Lua module: vim.diagnostic *diagnostic-api*
• {user_data}? (`any`) arbitrary data plugins can add • {user_data}? (`any`) arbitrary data plugins can add
• {namespace}? (`integer`) • {namespace}? (`integer`)
*vim.Diagnostic.Set*
Extends: |vim.Diagnostic|
Fields: ~
• {bufnr} (`nil`) Do not set. Will be overridden by
`vim.diagnostic.set()`.
• {namespace} (`nil`) Do not set. Will be overridden by
`vim.diagnostic.set()`.
• {col}? (`integer`, default: `0`) The starting column of the
diagnostic in bytes (0-indexed)
• {end_lnum}? (`integer`, default: same as `lnum`) The final line of
the diagnostic (0-indexed)
• {end_col}? (`integer`, default: same as `col`) The final column of
the diagnostic (0-indexed)
• {severity}? (`vim.diagnostic.Severity`, default: `vim.diagnostic.severity.ERROR`)
The severity of the diagnostic |vim.diagnostic.severity|
*vim.diagnostic.GetOpts* *vim.diagnostic.GetOpts*
A table with the following keys: A table with the following keys:
@@ -930,7 +948,7 @@ set({namespace}, {bufnr}, {diagnostics}, {opts}) *vim.diagnostic.set()*
Parameters: ~ Parameters: ~
• {namespace} (`integer`) The diagnostic namespace • {namespace} (`integer`) The diagnostic namespace
• {bufnr} (`integer`) Buffer number • {bufnr} (`integer`) Buffer number
• {diagnostics} (`vim.Diagnostic[]`) See |vim.Diagnostic|. • {diagnostics} (`vim.Diagnostic.Set[]`) See |vim.Diagnostic.Set|.
• {opts} (`vim.diagnostic.Opts?`) Display options to pass to • {opts} (`vim.diagnostic.Opts?`) Display options to pass to
|vim.diagnostic.show()|. See |vim.diagnostic.Opts|. |vim.diagnostic.show()|. See |vim.diagnostic.Opts|.

View File

@@ -23,22 +23,22 @@ end
--- @class vim.Diagnostic --- @class vim.Diagnostic
--- ---
--- Buffer number --- Buffer number
--- @field bufnr? integer --- @field bufnr integer
--- ---
--- The starting line of the diagnostic (0-indexed) --- The starting line of the diagnostic (0-indexed)
--- @field lnum integer --- @field lnum integer
--- ---
--- The final line of the diagnostic (0-indexed) --- The final line of the diagnostic (0-indexed)
--- @field end_lnum? integer --- @field end_lnum integer
--- ---
--- The starting column of the diagnostic (0-indexed) --- The starting column of the diagnostic (0-indexed)
--- @field col integer --- @field col integer
--- ---
--- The final column of the diagnostic (0-indexed) --- The final column of the diagnostic (0-indexed)
--- @field end_col? integer --- @field end_col integer
--- ---
--- The severity of the diagnostic |vim.diagnostic.severity| --- The severity of the diagnostic |vim.diagnostic.severity|
--- @field severity? vim.diagnostic.Severity --- @field severity vim.diagnostic.Severity
--- ---
--- The diagnostic text --- The diagnostic text
--- @field message string --- @field message string
@@ -56,6 +56,30 @@ end
--- ---
--- @field namespace? integer --- @field namespace? integer
--- @class vim.Diagnostic.Set : vim.Diagnostic
---
--- Do not set. Will be overridden by `vim.diagnostic.set()`.
--- @field bufnr nil
---
--- Do not set. Will be overridden by `vim.diagnostic.set()`.
--- @field namespace nil
---
--- The starting column of the diagnostic in bytes (0-indexed)
--- (default: `0`)
--- @field col? integer
---
--- The final line of the diagnostic (0-indexed)
--- (default: same as `lnum`)
--- @field end_lnum? integer
---
--- The final column of the diagnostic (0-indexed)
--- (default: same as `col`)
--- @field end_col? integer
---
--- The severity of the diagnostic |vim.diagnostic.severity|
--- (default: `vim.diagnostic.severity.ERROR`)
--- @field severity? vim.diagnostic.Severity
--- Many of the configuration options below accept one of the following: --- Many of the configuration options below accept one of the following:
--- - `false`: Disable this feature --- - `false`: Disable this feature
--- - `true`: Enable this feature, use default settings. --- - `true`: Enable this feature, use default settings.
@@ -319,21 +343,36 @@ M.severity = {
WARN = 2, WARN = 2,
INFO = 3, INFO = 3,
HINT = 4, HINT = 4,
}
--- @enum vim.diagnostic.SeverityName
local severity_invert = {
[1] = 'ERROR', [1] = 'ERROR',
[2] = 'WARN', [2] = 'WARN',
[3] = 'INFO', [3] = 'INFO',
[4] = 'HINT', [4] = 'HINT',
--- Mappings from qflist/loclist error types to severities
E = 1,
W = 2,
I = 3,
N = 4,
} }
--- @alias vim.diagnostic.SeverityInt 1|2|3|4 do
--- Set extra fields through table alias to hide from analysis tools
local s = M.severity --- @type table<any,any>
for i, name in ipairs(severity_invert) do
s[i] = name
end
--- Mappings from qflist/loclist error types to severities
s.E = 1
s.W = 2
s.I = 3
s.N = 4
end
--- See |diagnostic-severity| and |vim.diagnostic.get()| --- See |diagnostic-severity| and |vim.diagnostic.get()|
--- @alias vim.diagnostic.SeverityFilter vim.diagnostic.Severity|vim.diagnostic.Severity[]|{min:vim.diagnostic.Severity,max:vim.diagnostic.Severity} --- @alias vim.diagnostic.SeverityFilter
--- | vim.diagnostic.Severity
--- | vim.diagnostic.Severity[]
--- | {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
--- @type vim.diagnostic.Opts --- @type vim.diagnostic.Opts
local global_diagnostic_options = { local global_diagnostic_options = {
@@ -400,11 +439,11 @@ do
}) })
end end
--- @class (private) vim.diagnostic._extmark --- @class (private) vim.diagnostic._extmark : vim.api.keyset.get_extmark_item
--- @field [1] integer id --- @field [1] integer extmark_id
--- @field [2] integer start --- @field [2] integer row
--- @field [3] integer end --- @field [3] integer col
--- @field [4] table details --- @field [4] vim.api.keyset.extmark_details
--- @type table<integer,table<integer,vim.diagnostic._extmark[]>> --- @type table<integer,table<integer,vim.diagnostic._extmark[]>>
local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt)
@@ -427,26 +466,30 @@ local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
--- @type table<integer,vim.diagnostic.NS> --- @type table<integer,vim.diagnostic.NS>
local all_namespaces = {} local all_namespaces = {}
---@param severity string|vim.diagnostic.Severity ---@param severity string|vim.diagnostic.Severity?
---@return vim.diagnostic.Severity? ---@return vim.diagnostic.Severity?
local function to_severity(severity) local function to_severity(severity)
if type(severity) == 'string' then if type(severity) == 'string' then
assert(M.severity[string.upper(severity)], string.format('Invalid severity: %s', severity)) local ret = M.severity[severity:upper()] --[[@as vim.diagnostic.Severity?]]
return M.severity[string.upper(severity)] if not ret then
error(('Invalid severity: %s'):format(severity))
end
return ret
end end
return severity return severity --[[@as vim.diagnostic.Severity?]]
end end
--- @param severity vim.diagnostic.SeverityFilter --- @param severity vim.diagnostic.SeverityFilter
--- @return fun(vim.Diagnostic):boolean --- @return fun(d: vim.Diagnostic):boolean
local function severity_predicate(severity) local function severity_predicate(severity)
if type(severity) ~= 'table' then if type(severity) ~= 'table' then
severity = assert(to_severity(severity)) local severity0 = to_severity(severity)
---@param d vim.Diagnostic ---@param d vim.Diagnostic
return function(d) return function(d)
return d.severity == severity return d.severity == severity0
end end
end end
--- @diagnostic disable-next-line: undefined-field
if severity.min or severity.max then if severity.min or severity.max then
--- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity} --- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
local min_severity = to_severity(severity.min) or M.severity.HINT local min_severity = to_severity(severity.min) or M.severity.HINT
@@ -512,7 +555,7 @@ local function prefix_source(diagnostics)
end, diagnostics) end, diagnostics)
end end
--- @param format fun(vim.Diagnostic): string? --- @param format fun(diagnostic: vim.Diagnostic): string?
--- @param diagnostics vim.Diagnostic[] --- @param diagnostics vim.Diagnostic[]
--- @return vim.Diagnostic[] --- @return vim.Diagnostic[]
local function reformat_diagnostics(format, diagnostics) local function reformat_diagnostics(format, diagnostics)
@@ -584,7 +627,7 @@ local function get_resolved_options(opts, namespace, bufnr)
resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr) resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr)
end end
end end
return resolved return resolved --[[@as vim.diagnostic.OptsResolved]]
end end
-- Default diagnostic highlights -- Default diagnostic highlights
@@ -597,20 +640,17 @@ local diagnostic_severities = {
--- Make a map from vim.diagnostic.Severity -> Highlight Name --- Make a map from vim.diagnostic.Severity -> Highlight Name
--- @param base_name string --- @param base_name string
--- @return table<vim.diagnostic.SeverityInt,string> --- @return table<vim.diagnostic.Severity,string>
local function make_highlight_map(base_name) local function make_highlight_map(base_name)
local result = {} --- @type table<vim.diagnostic.SeverityInt,string> local result = {} --- @type table<vim.diagnostic.Severity,string>
for k in pairs(diagnostic_severities) do for k in pairs(diagnostic_severities) do
local name = M.severity[k] local name = severity_invert[k]
name = name:sub(1, 1) .. name:sub(2):lower() result[k] = ('Diagnostic%s%s%s'):format(base_name, name:sub(1, 1), name:sub(2):lower())
result[k] = 'Diagnostic' .. base_name .. name
end end
return result return result
end end
-- TODO(lewis6991): these highlight maps can only be indexed with an integer, however there usage
-- implies they can be indexed with any vim.diagnostic.Severity
local virtual_text_highlight_map = make_highlight_map('VirtualText') local virtual_text_highlight_map = make_highlight_map('VirtualText')
local virtual_lines_highlight_map = make_highlight_map('VirtualLines') local virtual_lines_highlight_map = make_highlight_map('VirtualLines')
local underline_highlight_map = make_highlight_map('Underline') local underline_highlight_map = make_highlight_map('Underline')
@@ -658,17 +698,24 @@ end
--- @param namespace integer --- @param namespace integer
--- @param bufnr integer --- @param bufnr integer
--- @param diagnostics vim.Diagnostic[] --- @param d vim.Diagnostic.Set
local function norm_diag(bufnr, namespace, d)
vim.validate('diagnostic.lnum', d.lnum, 'number')
local d1 = d --[[@as vim.Diagnostic]]
d1.severity = d.severity and to_severity(d.severity) or M.severity.ERROR
d1.end_lnum = d.end_lnum or d.lnum
d1.col = d.col or 0
d1.end_col = d.end_col or d.col or 0
d1.namespace = namespace
d1.bufnr = bufnr
end
--- @param namespace integer
--- @param bufnr integer
--- @param diagnostics vim.Diagnostic.Set[]
local function set_diagnostic_cache(namespace, bufnr, diagnostics) local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do for _, diagnostic in ipairs(diagnostics) do
assert(diagnostic.lnum, 'Diagnostic line number is required') norm_diag(bufnr, namespace, diagnostic)
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
diagnostic.namespace = namespace
diagnostic.bufnr = bufnr
end end
diagnostic_cache[bufnr][namespace] = diagnostics diagnostic_cache[bufnr][namespace] = diagnostics
end end
@@ -689,6 +736,7 @@ local function restore_extmarks(bufnr, last)
for _, extmark in ipairs(extmarks) do for _, extmark in ipairs(extmarks) do
if not found[extmark[1]] then if not found[extmark[1]] then
local opts = extmark[4] local opts = extmark[4]
--- @diagnostic disable-next-line: inject-field
opts.id = extmark[1] opts.id = extmark[1]
pcall(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
@@ -764,7 +812,7 @@ local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
--- @param namespace integer --- @param namespace integer
--- @param bufnr integer --- @param bufnr integer
--- @param args any[] --- @param args vim.diagnostic.OptsResolved
local function schedule_display(namespace, bufnr, args) local function schedule_display(namespace, bufnr, args)
bufs_waiting_to_update[bufnr][namespace] = args bufs_waiting_to_update[bufnr][namespace] = args
@@ -809,6 +857,7 @@ local function get_diagnostics(bufnr, opts, clamp)
---@cast namespace integer[] ---@cast namespace integer[]
--- @type vim.Diagnostic[]
local diagnostics = {} local diagnostics = {}
-- Memoized results of buf_line_count per bufnr -- Memoized results of buf_line_count per bufnr
@@ -860,7 +909,7 @@ local function get_diagnostics(bufnr, opts, clamp)
then then
d = vim.deepcopy(d, true) d = vim.deepcopy(d, true)
d.lnum = math.max(math.min(d.lnum, line_count), 0) d.lnum = math.max(math.min(d.lnum, line_count), 0)
d.end_lnum = math.max(math.min(assert(d.end_lnum), line_count), 0) d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
d.col = math.max(d.col, 0) d.col = math.max(d.col, 0)
d.end_col = math.max(d.end_col, 0) d.end_col = math.max(d.end_col, 0)
end end
@@ -877,13 +926,13 @@ local function get_diagnostics(bufnr, opts, clamp)
end end
end end
if namespace == nil and bufnr == nil then if not namespace and not bufnr then
for b, t in pairs(diagnostic_cache) do for buf, ns_diags in pairs(diagnostic_cache) do
for _, v in pairs(t) do for _, diags in pairs(ns_diags) do
add_all_diags(b, v) add_all_diags(buf, diags)
end end
end end
elseif namespace == nil then elseif not namespace then
bufnr = vim._resolve_bufnr(bufnr) bufnr = vim._resolve_bufnr(bufnr)
for iter_namespace in pairs(diagnostic_cache[bufnr]) do for iter_namespace in pairs(diagnostic_cache[bufnr]) do
add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace]) add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace])
@@ -981,6 +1030,7 @@ end
--- @return vim.Diagnostic? --- @return vim.Diagnostic?
local function next_diagnostic(search_forward, opts) local function next_diagnostic(search_forward, opts)
opts = opts or {} opts = opts or {}
--- @cast opts vim.diagnostic.JumpOpts1
-- Support deprecated win_id alias -- Support deprecated win_id alias
if opts.win_id then if opts.win_id then
@@ -1069,6 +1119,7 @@ local function goto_diagnostic(diagnostic, opts)
end end
opts = opts or {} opts = opts or {}
--- @cast opts vim.diagnostic.JumpOpts1
-- Support deprecated win_id alias -- Support deprecated win_id alias
if opts.win_id then if opts.win_id then
@@ -1089,7 +1140,7 @@ local function goto_diagnostic(diagnostic, opts)
if opts.float then if opts.float then
vim.deprecate('opts.float', 'opts.on_jump', '0.14') vim.deprecate('opts.float', 'opts.on_jump', '0.14')
local float_opts = opts.float ---@type table|boolean local float_opts = opts.float
float_opts = type(float_opts) == 'table' and float_opts or {} float_opts = type(float_opts) == 'table' and float_opts or {}
opts.on_jump = function(_, bufnr) opts.on_jump = function(_, bufnr)
@@ -1154,10 +1205,11 @@ function M.config(opts, namespace)
return vim.deepcopy(t, true) return vim.deepcopy(t, true)
end end
if opts.jump and opts.jump.float ~= nil then ---@diagnostic disable-line local jump_opts = opts.jump --[[@as vim.diagnostic.JumpOpts1]]
if jump_opts and jump_opts.float ~= nil then ---@diagnostic disable-line
vim.deprecate('opts.jump.float', 'opts.jump.on_jump', '0.14') vim.deprecate('opts.jump.float', 'opts.jump.on_jump', '0.14')
local float_opts = opts.jump.float ---@type table|boolean local float_opts = jump_opts.float
if float_opts then if float_opts then
float_opts = type(float_opts) == 'table' and float_opts or {} float_opts = type(float_opts) == 'table' and float_opts or {}
@@ -1198,7 +1250,7 @@ end
--- ---
---@param namespace integer The diagnostic namespace ---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number ---@param bufnr integer Buffer number
---@param diagnostics vim.Diagnostic[] ---@param diagnostics vim.Diagnostic.Set[]
---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()| ---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts) function M.set(namespace, bufnr, diagnostics, opts)
vim.validate('namespace', namespace, 'number') vim.validate('namespace', namespace, 'number')
@@ -1220,7 +1272,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
modeline = false, modeline = false,
buffer = bufnr, buffer = bufnr,
-- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get() -- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get()
data = { diagnostics = diagnostics }, data = { diagnostics = diagnostic_cache[bufnr][namespace] },
}) })
end end
@@ -1287,9 +1339,8 @@ function M.count(bufnr, opts)
local diagnostics = get_diagnostics(bufnr, opts, false) local diagnostics = get_diagnostics(bufnr, opts, false)
local count = {} --- @type table<integer,integer> local count = {} --- @type table<integer,integer>
for i = 1, #diagnostics do for _, d in ipairs(diagnostics) do
local severity = diagnostics[i].severity --[[@as integer]] count[d.severity] = (count[d.severity] or 0) + 1
count[severity] = (count[severity] or 0) + 1
end end
return count return count
end end
@@ -1415,6 +1466,12 @@ end
--- (default: `0`) --- (default: `0`)
--- @field winid? integer --- @field winid? integer
--- @nodoc
--- @class vim.diagnostic.JumpOpts1 : vim.diagnostic.JumpOpts
--- @field win_id? integer (deprecated) use winid
--- @field cursor_position? [integer, integer] (deprecated) use pos
--- @field float? table|boolean (deprecated) use on_jump
--- Move to a diagnostic. --- Move to a diagnostic.
--- ---
--- @param opts vim.diagnostic.JumpOpts --- @param opts vim.diagnostic.JumpOpts
@@ -1430,6 +1487,7 @@ function M.jump(opts)
-- Apply configuration options from vim.diagnostic.config() -- Apply configuration options from vim.diagnostic.config()
opts = vim.tbl_deep_extend('keep', opts, global_diagnostic_options.jump) opts = vim.tbl_deep_extend('keep', opts, global_diagnostic_options.jump)
--- @cast opts vim.diagnostic.JumpOpts1
if opts.diagnostic then if opts.diagnostic then
goto_diagnostic(opts.diagnostic, opts) goto_diagnostic(opts.diagnostic, opts)
@@ -1511,7 +1569,7 @@ M.handlers.signs = {
if opts.signs.text and opts.signs.text[k] then if opts.signs.text and opts.signs.text[k] then
text[k] = opts.signs.text[k] text[k] = opts.signs.text[k]
elseif type(k) == 'string' and not text[k] then elseif type(k) == 'string' and not text[k] then
text[k] = string.sub(k, 1, 1):upper() text[k] = k:sub(1, 1):upper()
end end
end end
@@ -1567,9 +1625,7 @@ M.handlers.underline = {
local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts) local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts)
for _, diagnostic in ipairs(diagnostics) do for _, diagnostic in ipairs(diagnostics) do
-- Default to error if we don't have a highlight associated local higroup = underline_highlight_map[diagnostic.severity]
local higroup = underline_highlight_map[assert(diagnostic.severity)]
or underline_highlight_map[vim.diagnostic.severity.ERROR]
if diagnostic._tags then if diagnostic._tags then
-- TODO(lewis6991): we should be able to stack these. -- TODO(lewis6991): we should be able to stack these.
@@ -1749,7 +1805,8 @@ local function render_virtual_lines(namespace, bufnr, diagnostics)
-- This loop reads each line, putting them into stacks with some extra data since -- This loop reads each line, putting them into stacks with some extra data since
-- rendering each line requires understanding what is beneath it. -- rendering each line requires understanding what is beneath it.
local ElementType = { Space = 1, Diagnostic = 2, Overlap = 3, Blank = 4 } ---@enum ElementType local ElementType = { Space = 1, Diagnostic = 2, Overlap = 3, Blank = 4 } ---@enum ElementType
local line_stacks = {} ---@type table<integer, {[1]:ElementType, [2]:string|vim.diagnostic.Severity|vim.Diagnostic}[]> ---@type table<integer, [ElementType, string|vim.diagnostic.Severity|vim.Diagnostic][]>
local line_stacks = {}
local prev_lnum = -1 local prev_lnum = -1
local prev_col = 0 local prev_col = 0
for _, diag in ipairs(diagnostics) do for _, diag in ipairs(diagnostics) do
@@ -1802,7 +1859,7 @@ local function render_virtual_lines(namespace, bufnr, diagnostics)
for i = #stack, 1, -1 do for i = #stack, 1, -1 do
if stack[i][1] == ElementType.Diagnostic then if stack[i][1] == ElementType.Diagnostic then
local diagnostic = stack[i][2] local diagnostic = stack[i][2]
local left = {} ---@type {[1]:string, [2]:string} local left = {} ---@type [string, string]
local overlap = false local overlap = false
local multi = false local multi = false
@@ -2124,13 +2181,13 @@ function M.show(namespace, bufnr, diagnostics, opts)
clear_scheduled_display(namespace, bufnr) clear_scheduled_display(namespace, bufnr)
else else
local mode = api.nvim_get_mode() local mode = api.nvim_get_mode()
if string.sub(mode.mode, 1, 1) == 'i' then if mode.mode:sub(1, 1) == 'i' then
schedule_display(namespace, bufnr, opts_res) schedule_display(namespace, bufnr, opts_res)
return return
end end
end end
if if_nil(opts_res.severity_sort, false) then if opts_res.severity_sort then
if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then
table.sort(diagnostics, function(a, b) table.sort(diagnostics, function(a, b)
return a.severity < b.severity return a.severity < b.severity
@@ -2237,19 +2294,19 @@ function M.open_float(opts, ...)
end end
local lines = {} --- @type string[] local lines = {} --- @type string[]
local highlights = {} --- @type table[] local highlights = {} --- @type { hlname: string, prefix?: { length: integer, hlname: string? }, suffix?: { length: integer, hlname: string? } }[]
local header = if_nil(opts.header, 'Diagnostics:') local header = if_nil(opts.header, 'Diagnostics:')
if header then if header then
vim.validate('header', header, { 'string', 'table' }, "'string' or 'table'") vim.validate('header', header, { 'string', 'table' }, "'string' or 'table'")
if type(header) == 'table' then if type(header) == 'table' then
-- Don't insert any lines for an empty string -- Don't insert any lines for an empty string
if string.len(if_nil(header[1], '')) > 0 then if #(header[1] or '') > 0 then
table.insert(lines, header[1]) lines[#lines + 1] = header[1]
table.insert(highlights, { hlname = header[2] or 'Bold' }) highlights[#highlights + 1] = { hlname = header[2] or 'Bold' }
end end
elseif #header > 0 then elseif #header > 0 then
table.insert(lines, header) lines[#lines + 1] = header
table.insert(highlights, { hlname = 'Bold' }) highlights[#highlights + 1] = { hlname = 'Bold' }
end end
end end
@@ -2261,10 +2318,11 @@ function M.open_float(opts, ...)
diagnostics = prefix_source(diagnostics) diagnostics = prefix_source(diagnostics)
end end
local prefix_opt = local prefix_opt = opts.prefix
if_nil(opts.prefix, (scope == 'cursor' and #diagnostics <= 1) and '' or function(_, i) or (scope == 'cursor' and #diagnostics <= 1) and ''
or function(_, i)
return string.format('%d. ', i) return string.format('%d. ', i)
end) end
local prefix, prefix_hl_group --- @type string?, string? local prefix, prefix_hl_group --- @type string?, string?
if prefix_opt then if prefix_opt then
@@ -2281,9 +2339,10 @@ function M.open_float(opts, ...)
end end
end end
local suffix_opt = if_nil(opts.suffix, function(diagnostic) local suffix_opt = opts.suffix
return diagnostic.code and string.format(' [%s]', diagnostic.code) or '' or function(diagnostic)
end) return diagnostic.code and string.format(' [%s]', diagnostic.code) or ''
end
local suffix, suffix_hl_group --- @type string?, string? local suffix, suffix_hl_group --- @type string?, string?
if suffix_opt then if suffix_opt then
@@ -2311,14 +2370,13 @@ function M.open_float(opts, ...)
local suffix0, suffix_hl_group0 = suffix_opt(diagnostic, i, #diagnostics) local suffix0, suffix_hl_group0 = suffix_opt(diagnostic, i, #diagnostics)
suffix, suffix_hl_group = suffix0 or '', suffix_hl_group0 or 'NormalFloat' suffix, suffix_hl_group = suffix0 or '', suffix_hl_group0 or 'NormalFloat'
end end
--- @type string? local hiname = floating_highlight_map[diagnostic.severity]
local hiname = floating_highlight_map[assert(diagnostic.severity)]
local message_lines = vim.split(diagnostic.message, '\n') local message_lines = vim.split(diagnostic.message, '\n')
for j = 1, #message_lines do for j = 1, #message_lines do
local pre = j == 1 and prefix or string.rep(' ', #prefix) local pre = j == 1 and prefix or string.rep(' ', #prefix)
local suf = j == #message_lines and suffix or '' local suf = j == #message_lines and suffix or ''
table.insert(lines, pre .. message_lines[j] .. suf) lines[#lines + 1] = pre .. message_lines[j] .. suf
table.insert(highlights, { highlights[#highlights + 1] = {
hlname = hiname, hlname = hiname,
prefix = { prefix = {
length = j == 1 and #prefix or 0, length = j == 1 and #prefix or 0,
@@ -2328,7 +2386,7 @@ function M.open_float(opts, ...)
length = j == #message_lines and #suffix or 0, length = j == #message_lines and #suffix or 0,
hlname = suffix_hl_group, hlname = suffix_hl_group,
}, },
}) }
end end
end end
@@ -2479,6 +2537,7 @@ function M.enable(enable, filter)
if not bufnr then if not bufnr then
if not ns_id then if not ns_id then
--- @type table<integer,true|table<integer,true>>
diagnostic_disabled = ( diagnostic_disabled = (
enable enable
-- Enable everything by setting diagnostic_disabled to an empty table. -- Enable everything by setting diagnostic_disabled to an empty table.
@@ -2503,9 +2562,8 @@ function M.enable(enable, filter)
if type(diagnostic_disabled[bufnr]) ~= 'table' then if type(diagnostic_disabled[bufnr]) ~= 'table' then
if enable then if enable then
return return
else
diagnostic_disabled[bufnr] = {}
end end
diagnostic_disabled[bufnr] = {}
end end
diagnostic_disabled[bufnr][ns_id] = (not enable) and true or nil diagnostic_disabled[bufnr][ns_id] = (not enable) and true or nil
end end
@@ -2560,7 +2618,7 @@ function M.match(str, pat, groups, severity_map, defaults)
return return
end end
local diagnostic = {} --- @type type<string,any> local diagnostic = {}
for i, match in ipairs(matches) do for i, match in ipairs(matches) do
local field = groups[i] local field = groups[i]

View File

@@ -80,13 +80,13 @@ end
---@param diagnostics lsp.Diagnostic[] ---@param diagnostics lsp.Diagnostic[]
---@param bufnr integer ---@param bufnr integer
---@param client_id integer ---@param client_id integer
---@return vim.Diagnostic[] ---@return vim.Diagnostic.Set[]
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr) local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
local position_encoding = client and client.offset_encoding or 'utf-16' local position_encoding = client and client.offset_encoding or 'utf-16'
--- @param diagnostic lsp.Diagnostic --- @param diagnostic lsp.Diagnostic
--- @return vim.Diagnostic --- @return vim.Diagnostic.Set
return vim.tbl_map(function(diagnostic) return vim.tbl_map(function(diagnostic)
local start = diagnostic.range.start local start = diagnostic.range.start
local _end = diagnostic.range['end'] local _end = diagnostic.range['end']
@@ -104,7 +104,7 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
if _end.line > start.line then if _end.line > start.line then
end_line = buf_lines and buf_lines[_end.line + 1] or '' end_line = buf_lines and buf_lines[_end.line + 1] or ''
end end
--- @type vim.Diagnostic --- @type vim.Diagnostic.Set
return { return {
lnum = start.line, lnum = start.line,
col = vim.str_byteindex(line, position_encoding, start.character, false), col = vim.str_byteindex(line, position_encoding, start.character, false),

View File

@@ -17,7 +17,7 @@ local M = {}
--- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs` --- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs`
--- Adds a diagnostic for node in the query buffer --- Adds a diagnostic for node in the query buffer
--- @param diagnostics vim.Diagnostic[] --- @param diagnostics vim.Diagnostic.Set[]
--- @param range Range4 --- @param range Range4
--- @param lint string --- @param lint string
--- @param lang string? --- @param lang string?
@@ -126,7 +126,7 @@ end)
--- @param match table<integer,TSNode[]> --- @param match table<integer,TSNode[]>
--- @param query vim.treesitter.Query --- @param query vim.treesitter.Query
--- @param lang_context QueryLinterLanguageContext --- @param lang_context QueryLinterLanguageContext
--- @param diagnostics vim.Diagnostic[] --- @param diagnostics vim.Diagnostic.Set[]
local function lint_match(buf, match, query, lang_context, diagnostics) local function lint_match(buf, match, query, lang_context, diagnostics)
local lang = lang_context.lang local lang = lang_context.lang
local parser_info = lang_context.parser_info local parser_info = lang_context.parser_info
@@ -162,7 +162,7 @@ function M.lint(buf, opts)
buf = api.nvim_get_current_buf() buf = api.nvim_get_current_buf()
end end
local diagnostics = {} local diagnostics = {} --- @type vim.Diagnostic.Set[]
local query = vim.treesitter.query.parse('query', lint_query) local query = vim.treesitter.query.parse('query', lint_query)
opts = normalize_opts(buf, opts) opts = normalize_opts(buf, opts)