diff options
author | Lewis Russell <lewis6991@gmail.com> | 2024-01-09 12:47:57 +0000 |
---|---|---|
committer | Lewis Russell <me@lewisr.dev> | 2024-01-16 09:33:10 +0000 |
commit | 50284d07b6f020c819aeb07bfb30d88453e63b6d (patch) | |
tree | cef294122443162d0962a8a32e2bfa884c053547 /runtime/lua/vim/diagnostic.lua | |
parent | 91ba9d0bf46aa82a7887737ae12ff2d5559214ca (diff) | |
download | rneovim-50284d07b6f020c819aeb07bfb30d88453e63b6d.tar.gz rneovim-50284d07b6f020c819aeb07bfb30d88453e63b6d.tar.bz2 rneovim-50284d07b6f020c819aeb07bfb30d88453e63b6d.zip |
fix(diagnostic): typing
Diffstat (limited to 'runtime/lua/vim/diagnostic.lua')
-rw-r--r-- | runtime/lua/vim/diagnostic.lua | 521 |
1 files changed, 365 insertions, 156 deletions
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 897837a5ce..c7a1e8ebd3 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -2,15 +2,87 @@ local api, if_nil = vim.api, vim.F.if_nil local M = {} ----@enum DiagnosticSeverity +--- @class vim.Diagnostic +--- @field bufnr? integer +--- @field lnum integer 0-indexed +--- @field end_lnum? integer 0-indexed +--- @field col integer 0-indexed +--- @field end_col? integer 0-indexed +--- @field severity? vim.diagnostic.Severity +--- @field message string +--- @field source? string +--- @field code? string +--- @field _tags? { deprecated: boolean, unnecessary: boolean} +--- @field user_data? any arbitrary data plugins can add +--- @field namespace? integer + +--- @class vim.diagnostic.Opts +--- @field float? boolean|vim.diagnostic.Opts.Float +--- @field update_in_insert? boolean +--- @field underline? boolean|vim.diagnostic.Opts.Underline +--- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText +--- @field signs? boolean|vim.diagnostic.Opts.Signs +--- @field severity_sort? boolean|{reverse?:boolean} + +--- @class vim.diagnostic.OptsResolved +--- @field float vim.diagnostic.Opts.Float +--- @field update_in_insert boolean +--- @field underline vim.diagnostic.Opts.Underline +--- @field virtual_text vim.diagnostic.Opts.VirtualText +--- @field signs vim.diagnostic.Opts.Signs +--- @field severity_sort {reverse?:boolean} + +--- @class vim.diagnostic.Opts.Float +--- @field bufnr? integer +--- @field namespace? integer +--- @field scope? 'line'|'buffer'|'cursor'|'c'|'l'|'b' +--- @field pos? integer|{[1]:integer,[2]:integer} +--- @field severity_sort? boolean|{reverse?:boolean} +--- @field severity? vim.diagnostic.Severity +--- @field header? string|{[1]:string,[2]:any} +--- @field source? boolean|string +--- @field format? fun(diagnostic:vim.Diagnostic): string +--- @field prefix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string) +--- @field suffix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string) +--- @field focus_id? string + +--- @class vim.diagnostic.Opts.Underline +--- @field severity? vim.diagnostic.Severity + +--- @class vim.diagnostic.Opts.VirtualText +--- @field severity? vim.diagnostic.Severity +--- @field source? boolean|string +--- @field prefix? string|function +--- @field suffix? string|function +--- @field spacing? integer +--- @field format? function +--- @field hl_mode? 'replace'|'combine'|'blend' +--- @field virt_text? {[1]:string,[2]:any}[] +--- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline' +--- @field virt_text_win_col? integer +--- @field virt_text_hide? boolean + +--- @class vim.diagnostic.Opts.Signs +--- @field severity? vim.diagnostic.Severity +--- @field priority? integer +--- @field text? table<vim.diagnostic.Severity,string> +--- @field numhl? table<vim.diagnostic.Severity,string> +--- @field linehl? table<vim.diagnostic.Severity,string> +--- @field texthl? table<vim.diagnostic.Severity,string> + +--- @enum vim.diagnostic.Severity M.severity = { ERROR = 1, WARN = 2, INFO = 3, HINT = 4, + [1] = 'ERROR', + [2] = 'WARN', + [3] = 'INFO', + [4] = 'HINT', } -vim.tbl_add_reverse_lookup(M.severity) +--- @alias vim.diagnostic.SeverityInt 1|2|3|4 -- Mappings from qflist/loclist error types to severities M.severity.E = M.severity.ERROR @@ -18,6 +90,7 @@ M.severity.W = M.severity.WARN M.severity.I = M.severity.INFO M.severity.N = M.severity.HINT +--- @type vim.diagnostic.Opts local global_diagnostic_options = { signs = true, underline = true, @@ -27,6 +100,11 @@ local global_diagnostic_options = { severity_sort = false, } +--- @class vim.diagnostic.Handler +--- @field show? fun(namespace: integer, bufnr: integer, diagnostics: vim.Diagnostic[], opts?: vim.diagnostic.OptsResolved) +--- @field hide? fun(namespace:integer, bufnr:integer) + +--- @type table<string,vim.diagnostic.Handler> M.handlers = setmetatable({}, { __newindex = function(t, name, handler) vim.validate({ handler = { handler, 't' } }) @@ -39,6 +117,9 @@ M.handlers = setmetatable({}, { -- Metatable that automatically creates an empty table when assigning to a missing key local bufnr_and_namespace_cacher_mt = { + --- @param t table<integer,table> + --- @param bufnr integer + --- @return table __index = function(t, bufnr) assert(bufnr > 0, 'Invalid buffer number') t[bufnr] = {} @@ -46,10 +127,13 @@ local bufnr_and_namespace_cacher_mt = { end, } -local diagnostic_cache +-- bufnr -> ns -> Diagnostic[] +local diagnostic_cache = {} --- @type table<integer,table<integer,vim.Diagnostic[]>> do local group = api.nvim_create_augroup('DiagnosticBufWipeout', {}) - diagnostic_cache = setmetatable({}, { + setmetatable(diagnostic_cache, { + --- @param t table<integer,vim.Diagnostic[]> + --- @param bufnr integer __index = function(t, bufnr) assert(bufnr > 0, 'Invalid buffer number') api.nvim_create_autocmd('BufWipeout', { @@ -65,13 +149,35 @@ do }) end +--- @class vim.diagnostic._extmark +--- @field [1] integer id +--- @field [2] integer start +--- @field [3] integer end +--- @field [4] table details + +--- @type table<integer,table<integer,vim.diagnostic._extmark[]>> local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) + +--- @type table<integer,true> local diagnostic_attached_buffers = {} + +--- @type table<integer,true|table<integer,true>> local diagnostic_disabled = {} + +--- @type table<integer,table<integer,table>> local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) +--- @class vim.diagnostic.NS +--- @field name string +--- @field opts vim.diagnostic.Opts +--- @field user_data table +--- @field disabled? boolean + +--- @type table<integer,vim.diagnostic.NS> local all_namespaces = {} +---@param severity string|vim.diagnostic.Severity +---@return vim.diagnostic.Severity? local function to_severity(severity) if type(severity) == 'string' then return assert( @@ -82,45 +188,56 @@ local function to_severity(severity) return severity end +--- @param severity vim.diagnostic.Severity|vim.diagnostic.Severity[]|{min:vim.diagnostic.Severity,max:vim.diagnostic.Severity} +--- @param diagnostics vim.Diagnostic[] +--- @return vim.Diagnostic[] local function filter_by_severity(severity, diagnostics) if not severity then return diagnostics end if type(severity) ~= 'table' then - severity = to_severity(severity) + severity = assert(to_severity(severity)) + --- @param t vim.Diagnostic return vim.tbl_filter(function(t) return t.severity == severity end, diagnostics) end if severity.min or severity.max then + --- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity} local min_severity = to_severity(severity.min) or M.severity.HINT local max_severity = to_severity(severity.max) or M.severity.ERROR + --- @param t vim.Diagnostic return vim.tbl_filter(function(t) return t.severity <= min_severity and t.severity >= max_severity end, diagnostics) end - local severities = {} + --- @cast severity vim.diagnostic.Severity[] + + local severities = {} --- @type table<vim.diagnostic.Severity,true> for _, s in ipairs(severity) do - severities[to_severity(s)] = true + severities[assert(to_severity(s))] = true end - --- @param t table + --- @param t vim.Diagnostic return vim.tbl_filter(function(t) return severities[t.severity] end, diagnostics) end +--- @param bufnr integer +--- @return integer local function count_sources(bufnr) - local seen = {} + local seen = {} --- @type table<string,true> local count = 0 for _, namespace_diagnostics in pairs(diagnostic_cache[bufnr]) do for _, diagnostic in ipairs(namespace_diagnostics) do - if diagnostic.source and not seen[diagnostic.source] then - seen[diagnostic.source] = true + local source = diagnostic.source + if source and not seen[source] then + seen[source] = true count = count + 1 end end @@ -128,7 +245,10 @@ local function count_sources(bufnr) return count end +--- @param diagnostics vim.Diagnostic[] +--- @return vim.Diagnostic[] local function prefix_source(diagnostics) + --- @param d vim.Diagnostic return vim.tbl_map(function(d) if not d.source then return d @@ -140,6 +260,8 @@ local function prefix_source(diagnostics) end, diagnostics) end +--- @param diagnostics vim.Diagnostic[] +--- @return vim.Diagnostic[] local function reformat_diagnostics(format, diagnostics) vim.validate({ format = { format, 'f' }, @@ -153,26 +275,35 @@ local function reformat_diagnostics(format, diagnostics) return formatted end +--- @param option string +--- @param namespace integer? +--- @return table local function enabled_value(option, namespace) local ns = namespace and M.get_namespace(namespace) or {} if ns.opts and type(ns.opts[option]) == 'table' then return ns.opts[option] end - if type(global_diagnostic_options[option]) == 'table' then - return global_diagnostic_options[option] + local global_opt = global_diagnostic_options[option] + if type(global_opt) == 'table' then + return global_opt end return {} end +--- @param option string +--- @param value any? +--- @param namespace integer? +--- @param bufnr integer +--- @return any local function resolve_optional_value(option, value, namespace, bufnr) if not value then return false elseif value == true then return enabled_value(option, namespace) elseif type(value) == 'function' then - local val = value(namespace, bufnr) + local val = value(namespace, bufnr) --- @type any if val == true then return enabled_value(option, namespace) else @@ -180,15 +311,18 @@ local function resolve_optional_value(option, value, namespace, bufnr) end elseif type(value) == 'table' then return value - else - error('Unexpected option type: ' .. vim.inspect(value)) end + error('Unexpected option type: ' .. vim.inspect(value)) end +--- @param opts vim.diagnostic.Opts? +--- @param namespace integer? +--- @param bufnr integer +--- @return vim.diagnostic.OptsResolved local function get_resolved_options(opts, namespace, bufnr) local ns = namespace and M.get_namespace(namespace) or {} -- 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) + local resolved = vim.tbl_extend('keep', opts or {}, ns.opts or {}, global_diagnostic_options) --- @type table<string,any> for k in pairs(global_diagnostic_options) do if resolved[k] ~= nil then resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr) @@ -205,9 +339,11 @@ local diagnostic_severities = { [M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' }, } --- Make a map from DiagnosticSeverity -> Highlight Name +--- Make a map from vim.diagnostic.Severity -> Highlight Name +--- @param base_name string +--- @return table<vim.diagnostic.SeverityInt,string> local function make_highlight_map(base_name) - local result = {} + local result = {} --- @type table<vim.diagnostic.SeverityInt,string> for k in pairs(diagnostic_severities) do local name = M.severity[k] name = name:sub(1, 1) .. name:sub(2):lower() @@ -217,6 +353,8 @@ local function make_highlight_map(base_name) return result 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 underline_highlight_map = make_highlight_map('Underline') local floating_highlight_map = make_highlight_map('Floating') @@ -229,12 +367,14 @@ local function get_bufnr(bufnr) return bufnr end +--- @param diagnostics vim.Diagnostic[] +--- @return table<integer,vim.Diagnostic[]> local function diagnostic_lines(diagnostics) if not diagnostics then return {} end - local diagnostics_by_line = {} + local diagnostics_by_line = {} --- @type table<integer,vim.Diagnostic[]> for _, diagnostic in ipairs(diagnostics) do local line_diagnostics = diagnostics_by_line[diagnostic.lnum] if not line_diagnostics then @@ -246,6 +386,9 @@ local function diagnostic_lines(diagnostics) return diagnostics_by_line end +--- @param namespace integer +--- @param bufnr integer +--- @param diagnostics vim.Diagnostic[] local function set_diagnostic_cache(namespace, bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do assert(diagnostic.lnum, 'Diagnostic line number is required') @@ -260,10 +403,12 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics) diagnostic_cache[bufnr][namespace] = diagnostics end +--- @param bufnr integer +--- @param last integer local function restore_extmarks(bufnr, last) for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) - local found = {} + local found = {} --- @type table<integer,true> for _, extmark in ipairs(extmarks_current) do -- nvim_buf_set_lines will move any extmark to the line after the last -- nvim_buf_set_text will move any extmark to the last line @@ -281,6 +426,8 @@ local function restore_extmarks(bufnr, last) end end +--- @param namespace integer +--- @param bufnr? integer local function save_extmarks(namespace, bufnr) bufnr = get_bufnr(bufnr) if not diagnostic_attached_buffers[bufnr] then @@ -298,6 +445,7 @@ local function save_extmarks(namespace, bufnr) api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true }) end +--- @type table<string,true> local registered_autocmds = {} local function make_augroup_key(namespace, bufnr) @@ -305,6 +453,8 @@ local function make_augroup_key(namespace, bufnr) return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name) end +--- @param namespace integer +--- @param bufnr integer local function execute_scheduled_display(namespace, bufnr) local args = bufs_waiting_to_update[bufnr][namespace] if not args then @@ -320,6 +470,9 @@ end --- Table of autocmd events to fire the update for displaying new diagnostic information local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' } +--- @param namespace integer +--- @param bufnr integer +--- @param args any[] local function schedule_display(namespace, bufnr, args) bufs_waiting_to_update[bufnr][namespace] = args @@ -338,6 +491,8 @@ local function schedule_display(namespace, bufnr, args) end end +--- @param namespace integer +--- @param bufnr integer local function clear_scheduled_display(namespace, bufnr) local key = make_augroup_key(namespace, bufnr) @@ -347,6 +502,10 @@ local function clear_scheduled_display(namespace, bufnr) end end +--- @param bufnr integer? +--- @param opts vim.diagnostic.GetOpts? +--- @param clamp boolean +--- @return vim.Diagnostic[] local function get_diagnostics(bufnr, opts, clamp) opts = opts or {} @@ -354,13 +513,19 @@ local function get_diagnostics(bufnr, opts, clamp) local diagnostics = {} -- Memoized results of buf_line_count per bufnr + --- @type table<integer,integer> local buf_line_count = setmetatable({}, { + --- @param t table<integer,integer> + --- @param k integer + --- @return integer __index = function(t, k) t[k] = api.nvim_buf_line_count(k) return rawget(t, k) end, }) + ---@param b integer + ---@param d vim.Diagnostic local function add(b, d) if not opts.lnum or d.lnum == opts.lnum then if clamp and api.nvim_buf_is_loaded(b) then @@ -375,7 +540,7 @@ local function get_diagnostics(bufnr, opts, clamp) then d = vim.deepcopy(d, true) d.lnum = math.max(math.min(d.lnum, line_count), 0) - d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0) + d.end_lnum = math.max(math.min(assert(d.end_lnum), line_count), 0) d.col = math.max(d.col, 0) d.end_col = math.max(d.end_col, 0) end @@ -384,6 +549,8 @@ local function get_diagnostics(bufnr, opts, clamp) end end + --- @param buf integer + --- @param diags vim.Diagnostic[] local function add_all_diags(buf, diags) for _, diagnostic in pairs(diags) do add(buf, diagnostic) @@ -417,18 +584,20 @@ local function get_diagnostics(bufnr, opts, clamp) return diagnostics end +--- @param loclist boolean +--- @param opts vim.diagnostic.setqflist.Opts|vim.diagnostic.setloclist.Opts? local function set_list(loclist, opts) opts = opts or {} - local open = vim.F.if_nil(opts.open, true) + local open = if_nil(opts.open, true) local title = opts.title or 'Diagnostics' local winnr = opts.winnr or 0 - local bufnr + local bufnr --- @type integer? if loclist then 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 - local diagnostics = get_diagnostics(bufnr, opts, false) + local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], false) local items = M.toqflist(diagnostics) if loclist then vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items }) @@ -440,10 +609,16 @@ local function set_list(loclist, opts) end end +--- @param position {[1]: integer, [2]: integer} +--- @param search_forward boolean +--- @param bufnr integer +--- @param opts vim.diagnostic.GotoOpts +--- @param namespace integer +--- @return vim.Diagnostic? 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 wrap = if_nil(opts.wrap, true) local line_count = api.nvim_buf_line_count(bufnr) local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) @@ -460,6 +635,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) end if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] + --- @type function, function local sort_diagnostics, is_next if search_forward then sort_diagnostics = function(a, b) @@ -478,7 +654,9 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) end table.sort(line_diagnostics[lnum], sort_diagnostics) if i == 0 then - for _, v in pairs(line_diagnostics[lnum]) do + for _, v in + pairs(line_diagnostics[lnum] --[[@as table<string,any>]]) + do if is_next(v) then return v end @@ -490,10 +668,12 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) end end +--- @param opts vim.diagnostic.GotoOpts? +--- @param pos {[1]:integer,[2]:integer}|false local function diagnostic_move_pos(opts, pos) opts = opts or {} - local float = vim.F.if_nil(opts.float, true) + local float = if_nil(opts.float, true) local win_id = opts.win_id or api.nvim_get_current_win() if not pos then @@ -549,8 +729,8 @@ end --- - `table`: Enable this feature with overrides. Use an empty table to use default values. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above. --- ----@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a ---- configuration table with the following keys: +---@param opts vim.diagnostic.Opts? (table?) When omitted or "nil", retrieve the current +--- configuration. Otherwise, a configuration table with the following keys: --- - underline: (default true) Use underline for diagnostics. Options: --- * severity: Only underline diagnostics matching the given --- severity |diagnostic-severity| @@ -616,17 +796,17 @@ end --- Options: --- * reverse: (boolean) Reverse sort order --- ----@param namespace integer|nil Update the options for the given namespace. When omitted, update the ---- global diagnostic options. +---@param namespace integer? Update the options for the given namespace. When omitted, update the +--- global diagnostic options. --- ----@return table|nil table of current diagnostic config if `opts` is omitted. +---@return vim.diagnostic.Opts? (table?) table of current diagnostic config if `opts` is omitted. function M.config(opts, namespace) vim.validate({ opts = { opts, 't', true }, namespace = { namespace, 'n', true }, }) - local t + local t --- @type vim.diagnostic.Opts if namespace then local ns = M.get_namespace(namespace) t = ns.opts @@ -639,7 +819,9 @@ function M.config(opts, namespace) return vim.deepcopy(t, true) end - for k, v in pairs(opts) do + for k, v in + pairs(opts --[[@as table<any,any>]]) + do t[k] = v end @@ -662,8 +844,8 @@ end --- ---@param namespace integer The diagnostic namespace ---@param bufnr integer Buffer number ----@param diagnostics table A list of diagnostic items |diagnostic-structure| ----@param opts table|nil Display options to pass to |vim.diagnostic.show()| +---@param diagnostics vim.Diagnostic[] A list of diagnostic items |diagnostic-structure| +---@param opts? vim.diagnostic.Opts (table) Display options to pass to |vim.diagnostic.show()| function M.set(namespace, bufnr, diagnostics, opts) vim.validate({ namespace = { namespace, 'n' }, @@ -689,6 +871,7 @@ function M.set(namespace, bufnr, diagnostics, opts) api.nvim_exec_autocmds('DiagnosticChanged', { modeline = false, buffer = bufnr, + -- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get() data = { diagnostics = diagnostics }, }) end @@ -696,11 +879,11 @@ end --- Get namespace metadata. --- ---@param namespace integer Diagnostic namespace ----@return table Namespace metadata +---@return vim.diagnostic.NS (table) Namespace metadata function M.get_namespace(namespace) vim.validate({ namespace = { namespace, 'n' } }) if not all_namespaces[namespace] then - local name + local name --- @type string? for k, v in pairs(api.nvim_get_namespaces()) do if namespace == v then name = k @@ -721,35 +904,22 @@ end --- Get current diagnostic namespaces. --- ----@return table A list of active diagnostic namespaces |vim.diagnostic|. +---@return table<integer,vim.diagnostic.NS> A list of active diagnostic namespaces |vim.diagnostic|. function M.get_namespaces() return vim.deepcopy(all_namespaces, true) end ----@class Diagnostic ----@field bufnr? integer ----@field lnum integer 0-indexed ----@field end_lnum? integer 0-indexed ----@field col integer 0-indexed ----@field end_col? integer 0-indexed ----@field severity? DiagnosticSeverity ----@field message string ----@field source? string ----@field code? string ----@field _tags? { deprecated: boolean, unnecessary: boolean} ----@field user_data? any arbitrary data plugins can add - --- Get current diagnostics. --- --- Modifying diagnostics in the returned table has no effect. To set diagnostics in a buffer, use |vim.diagnostic.set()|. --- ----@param bufnr integer|nil Buffer number to get diagnostics from. Use 0 for +---@param bufnr integer? Buffer number to get diagnostics from. Use 0 for --- current buffer or nil for all buffers. ----@param opts table|nil A table with the following keys: +---@param opts? vim.diagnostic.GetOpts (table) A table with the following keys: --- - namespace: (number) Limit diagnostics to the given namespace. --- - lnum: (number) Limit diagnostics to the given line number. --- - severity: See |diagnostic-severity|. ----@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present. +---@return vim.Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present. function M.get(bufnr, opts) vim.validate({ bufnr = { bufnr, 'n', true }, @@ -761,12 +931,12 @@ end --- Get current diagnostics count. --- ----@param bufnr integer|nil Buffer number to get diagnostics from. Use 0 for ---- current buffer or nil for all buffers. ----@param opts table|nil A table with the following keys: ---- - namespace: (number) Limit diagnostics to the given namespace. ---- - lnum: (number) Limit diagnostics to the given line number. ---- - severity: See |diagnostic-severity|. +---@param bufnr? integer Buffer number to get diagnostics from. Use 0 for +--- current buffer or nil for all buffers. +---@param opts? table A table with the following keys: +--- - namespace: (number) Limit diagnostics to the given namespace. +--- - lnum: (number) Limit diagnostics to the given line number. +--- - severity: See |diagnostic-severity|. ---@return table A table with actually present severity values as keys (see |diagnostic-severity|) and integer counts as values. function M.count(bufnr, opts) vim.validate({ @@ -775,9 +945,9 @@ function M.count(bufnr, opts) }) local diagnostics = get_diagnostics(bufnr, opts, false) - local count = {} + local count = {} --- @type table<integer,integer> for i = 1, #diagnostics do - local severity = diagnostics[i].severity + local severity = diagnostics[i].severity --[[@as integer]] count[severity] = (count[severity] or 0) + 1 end return count @@ -785,8 +955,8 @@ end --- Get the previous diagnostic closest to the cursor position. --- ----@param opts nil|table See |vim.diagnostic.goto_next()| ----@return Diagnostic|nil Previous diagnostic +---@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()| +---@return vim.Diagnostic? Previous diagnostic function M.get_prev(opts) opts = opts or {} @@ -799,9 +969,9 @@ end --- Return the position of the previous diagnostic in the current buffer. --- ----@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 +---@param opts? vim.diagnostic.GotoOpts (table) 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 @@ -812,15 +982,15 @@ function M.get_prev_pos(opts) end --- Move to the previous diagnostic in the current buffer. ----@param opts table|nil See |vim.diagnostic.goto_next()| +---@param opts? vim.diagnostic.GotoOpts (table) 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|nil See |vim.diagnostic.goto_next()| ----@return Diagnostic|nil Next diagnostic +---@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()| +---@return vim.Diagnostic? : Next diagnostic function M.get_next(opts) opts = opts or {} @@ -833,9 +1003,9 @@ end --- Return the position of the next diagnostic in the current buffer. --- ----@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. +---@param opts? vim.diagnostic.GotoOpts (table) 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 @@ -845,10 +1015,21 @@ function M.get_next_pos(opts) return { next.lnum, next.col } end +--- @class vim.diagnostic.GetOpts +--- @field namespace? integer +--- @field lnum? integer +--- @field severity? vim.diagnostic.Severity + +--- @class vim.diagnostic.GotoOpts : vim.diagnostic.GetOpts +--- @field cursor_position? {[1]:integer,[2]:integer} +--- @field wrap? integer +--- @field float? boolean|vim.diagnostic.Opts.Float +--- @field win_id? integer + --- Move to the next diagnostic. --- ----@param opts table|nil Configuration table with the following keys: ---- - namespace: (number) Only consider diagnostics from the given namespace. +---@param opts? vim.diagnostic.GotoOpts (table) Configuration table with the following keys: +--- - namespace: (integer) Only consider diagnostics from the given namespace. --- - cursor_position: (cursor position) Cursor position as a (row, col) tuple. --- See |nvim_win_get_cursor()|. Defaults to the current cursor position. --- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'. @@ -860,7 +1041,7 @@ end --- the "scope" option). --- - win_id: (number, default 0) Window ID function M.goto_next(opts) - return diagnostic_move_pos(opts, M.get_next_pos(opts)) + diagnostic_move_pos(opts, M.get_next_pos(opts)) end M.handlers.signs = { @@ -885,7 +1066,7 @@ M.handlers.signs = { -- 10 is the default sign priority when none is explicitly specified local priority = opts.signs and opts.signs.priority or 10 - local get_priority + local get_priority --- @type function if opts.severity_sort then if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then get_priority = function(severity) @@ -951,7 +1132,7 @@ M.handlers.signs = { end end - local text = {} ---@type table<string, string> + local text = {} ---@type table<vim.diagnostic.Severity|string, string> for k in pairs(M.severity) do if opts.signs.text and opts.signs.text[k] then text[k] = opts.signs.text[k] @@ -975,6 +1156,9 @@ M.handlers.signs = { end end end, + + --- @param namespace integer + --- @param bufnr integer hide = function(namespace, bufnr) local ns = M.get_namespace(namespace) if ns.user_data.sign_ns and api.nvim_buf_is_valid(bufnr) then @@ -1015,10 +1199,12 @@ M.handlers.underline = { local underline_ns = ns.user_data.underline_ns for _, diagnostic in ipairs(diagnostics) do - local higroup = underline_highlight_map[diagnostic.severity] + --- @type string? + local higroup = underline_highlight_map[assert(diagnostic.severity)] if higroup == nil then -- Default to error if we don't have a highlight associated + -- TODO(lewis6991): this is always nil since underline_highlight_map only has integer keys higroup = underline_highlight_map.Error end @@ -1074,7 +1260,7 @@ M.handlers.virtual_text = { return end - local severity + local severity --- @type vim.diagnostic.Severity? if opts.virtual_text then if opts.virtual_text.format then diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics) @@ -1132,7 +1318,9 @@ M.handlers.virtual_text = { --- Exported for backward compatibility with --- vim.lsp.diagnostic.get_virtual_text_chunks_for_line(). When that function is eventually removed, --- this can be made local. ----@private +--- @private +--- @param line_diags table<integer,vim.Diagnostic> +--- @param opts vim.diagnostic.Opts.VirtualText function M._get_virt_text_chunks(line_diags, opts) if #line_diags == 0 then return nil @@ -1182,10 +1370,10 @@ end --- To hide diagnostics and prevent them from re-displaying, use --- |vim.diagnostic.disable()|. --- ----@param namespace integer|nil Diagnostic namespace. When omitted, hide ---- diagnostics from all namespaces. ----@param bufnr integer|nil Buffer number, or 0 for current buffer. When ---- omitted, hide diagnostics in all buffers. +---@param namespace integer? Diagnostic namespace. When omitted, hide +--- diagnostics from all namespaces. +---@param bufnr integer? Buffer number, or 0 for current buffer. When +--- omitted, hide diagnostics in all buffers. function M.hide(namespace, bufnr) vim.validate({ namespace = { namespace, 'n', true }, @@ -1207,11 +1395,11 @@ end --- Check whether diagnostics are disabled in a given buffer. --- ----@param bufnr integer|nil Buffer number, or 0 for current buffer. ----@param namespace integer|nil Diagnostic namespace. When omitted, checks if ---- all diagnostics are disabled in {bufnr}. ---- Otherwise, only checks if diagnostics from ---- {namespace} are disabled. +---@param bufnr integer? Buffer number, or 0 for current buffer. +---@param namespace integer? 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) @@ -1228,17 +1416,17 @@ end --- Display diagnostics for the given namespace and buffer. --- ----@param namespace integer|nil Diagnostic namespace. When omitted, show ---- diagnostics from all namespaces. ----@param bufnr integer|nil Buffer number, or 0 for current buffer. When omitted, show ---- diagnostics in all buffers. ----@param diagnostics table|nil The diagnostics to display. When omitted, use the +---@param namespace integer? Diagnostic namespace. When omitted, show +--- diagnostics from all namespaces. +---@param bufnr integer? Buffer number, or 0 for current buffer. When omitted, show +--- diagnostics in all buffers. +---@param diagnostics vim.Diagnostic[]? The diagnostics to display. When omitted, use the --- saved diagnostics for the given namespace and --- buffer. This can be used to display a list of diagnostics --- without saving them or to display only a subset of --- diagnostics. May not be used when {namespace} --- or {bufnr} is nil. ----@param opts table|nil Display options. See |vim.diagnostic.config()|. +---@param opts? vim.diagnostic.Opts (table) Display options. See |vim.diagnostic.config()|. function M.show(namespace, bufnr, diagnostics, opts) vim.validate({ namespace = { namespace, 'n', true }, @@ -1277,24 +1465,24 @@ function M.show(namespace, bufnr, diagnostics, opts) diagnostics = diagnostics or get_diagnostics(bufnr, { namespace = namespace }, true) - if not diagnostics or vim.tbl_isempty(diagnostics) then + if vim.tbl_isempty(diagnostics) then return end - opts = get_resolved_options(opts, namespace, bufnr) + local opts_res = get_resolved_options(opts, namespace, bufnr) - if opts.update_in_insert then + if opts_res.update_in_insert then clear_scheduled_display(namespace, bufnr) else local mode = api.nvim_get_mode() if string.sub(mode.mode, 1, 1) == 'i' then - schedule_display(namespace, bufnr, opts) + schedule_display(namespace, bufnr, opts_res) return end end - if vim.F.if_nil(opts.severity_sort, false) then - if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then + if if_nil(opts_res.severity_sort, false) then + if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then table.sort(diagnostics, function(a, b) return a.severity < b.severity end) @@ -1306,15 +1494,15 @@ function M.show(namespace, bufnr, diagnostics, opts) end for handler_name, handler in pairs(M.handlers) do - if handler.show and opts[handler_name] then - handler.show(namespace, bufnr, diagnostics, opts) + if handler.show and opts_res[handler_name] then + handler.show(namespace, bufnr, diagnostics, opts_res) end end end --- Show diagnostics in a floating window. --- ----@param opts table|nil Configuration table with the same keys +---@param opts vim.diagnostic.Opts.Float? (table?) Configuration table with the same keys --- as |vim.lsp.util.open_floating_preview()| in addition to the following: --- - bufnr: (number) Buffer number to show diagnostics from. --- Defaults to the current buffer. @@ -1354,13 +1542,13 @@ end --- Overrides the setting from |vim.diagnostic.config()|. --- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of --- prepending it. Overrides the setting from |vim.diagnostic.config()|. ----@return integer|nil, integer|nil: ({float_bufnr}, {win_id}) +---@return integer?, integer?: ({float_bufnr}, {win_id}) function M.open_float(opts, ...) -- Support old (bufnr, opts) signature - local bufnr + local bufnr --- @type integer? if opts == nil or type(opts) == 'number' then bufnr = opts - opts = ... + opts = ... --- @type vim.diagnostic.Opts.Float else vim.validate({ opts = { opts, 't', true }, @@ -1382,16 +1570,17 @@ function M.open_float(opts, ...) end local scope = ({ l = 'line', c = 'cursor', b = 'buffer' })[opts.scope] or opts.scope or 'line' - local lnum, col + local lnum, col --- @type integer, integer + local opts_pos = opts.pos if scope == 'line' or scope == 'cursor' then - if not opts.pos then + if not opts_pos then local pos = 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) + elseif type(opts_pos) == 'number' then + lnum = opts_pos + elseif type(opts_pos) == 'table' then + lnum, col = opts_pos[1], opts_pos[2] else error("Invalid value for option 'pos'") end @@ -1399,15 +1588,17 @@ function M.open_float(opts, ...) error("Invalid value for option 'scope'") end - local diagnostics = get_diagnostics(bufnr, opts, true) + local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], true) if scope == 'line' then + --- @param d vim.Diagnostic diagnostics = vim.tbl_filter(function(d) return d.lnum == lnum end, diagnostics) elseif scope == 'cursor' then -- LSP servers can send diagnostics with `end_col` past the length of the line local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] + --- @param d vim.Diagnostic diagnostics = vim.tbl_filter(function(d) return d.lnum == lnum and math.min(d.col, line_length - 1) <= col @@ -1419,7 +1610,7 @@ function M.open_float(opts, ...) return end - local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort) + local severity_sort = if_nil(opts.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) @@ -1432,8 +1623,8 @@ function M.open_float(opts, ...) end end - local lines = {} - local highlights = {} + local lines = {} --- @type string[] + local highlights = {} --- @type table[] local header = if_nil(opts.header, 'Diagnostics:') if header then vim.validate({ @@ -1468,7 +1659,7 @@ function M.open_float(opts, ...) return string.format('%d. ', i) end) - local prefix, prefix_hl_group + local prefix, prefix_hl_group --- @type string?, string? if prefix_opt then vim.validate({ prefix = { @@ -1488,7 +1679,7 @@ function M.open_float(opts, ...) return diagnostic.code and string.format(' [%s]', diagnostic.code) or '' end) - local suffix, suffix_hl_group + local suffix, suffix_hl_group --- @type string?, string? if suffix_opt then vim.validate({ suffix = { @@ -1505,15 +1696,18 @@ function M.open_float(opts, ...) 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' + if type(prefix_opt) == 'function' then + --- @cast prefix_opt fun(...): string?, string? + local prefix0, prefix_hl_group0 = prefix_opt(diagnostic, i, #diagnostics) + prefix, prefix_hl_group = prefix0 or '', prefix_hl_group0 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' + if type(suffix_opt) == 'function' then + --- @cast suffix_opt fun(...): string?, string? + local suffix0, suffix_hl_group0 = suffix_opt(diagnostic, i, #diagnostics) + suffix, suffix_hl_group = suffix0 or '', suffix_hl_group0 or 'NormalFloat' end - local hiname = floating_highlight_map[diagnostic.severity] + --- @type string? + local hiname = floating_highlight_map[assert(diagnostic.severity)] local message_lines = vim.split(diagnostic.message, '\n') for j = 1, #message_lines do local pre = j == 1 and prefix or string.rep(' ', #prefix) @@ -1561,10 +1755,10 @@ end --- simply remove diagnostic decorations in a way that they can be --- re-displayed, use |vim.diagnostic.hide()|. --- ----@param namespace integer|nil Diagnostic namespace. When omitted, remove ---- diagnostics from all namespaces. ----@param bufnr integer|nil Remove diagnostics for the given buffer. When omitted, ---- diagnostics are removed for all buffers. +---@param namespace integer? Diagnostic namespace. When omitted, remove +--- diagnostics from all namespaces. +---@param bufnr integer? Remove diagnostics for the given buffer. When omitted, +--- diagnostics are removed for all buffers. function M.reset(namespace, bufnr) vim.validate({ namespace = { namespace, 'n', true }, @@ -1591,9 +1785,15 @@ function M.reset(namespace, bufnr) end end +--- @class vim.diagnostic.setqflist.Opts +--- @field namespace? integer +--- @field open? boolean +--- @field title? string +--- @field severity? vim.diagnostic.Severity + --- Add all diagnostics to the quickfix list. --- ----@param opts table|nil Configuration table with the following keys: +---@param opts? vim.diagnostic.setqflist.Opts (table) Configuration table with the following keys: --- - namespace: (number) Only add diagnostics from the given namespace. --- - open: (boolean, default true) Open quickfix list after setting. --- - title: (string) Title of quickfix list. Defaults to "Diagnostics". @@ -1602,9 +1802,16 @@ function M.setqflist(opts) set_list(false, opts) end +--- @class vim.diagnostic.setloclist.Opts +--- @field namespace? integer +--- @field open? boolean +--- @field title? string +--- @field severity? vim.diagnostic.Severity +--- @field winnr? integer + --- Add buffer diagnostics to the location list. --- ----@param opts table|nil Configuration table with the following keys: +---@param opts? vim.diagnostic.setloclist.Opts (table) Configuration table with the following keys: --- - namespace: (number) Only add diagnostics from the given namespace. --- - winnr: (number, default 0) Window number to set location list for. --- - open: (boolean, default true) Open the location list after setting. @@ -1616,9 +1823,9 @@ end --- Disable diagnostics in the given buffer. --- ----@param bufnr integer|nil Buffer number, or 0 for current buffer. When ---- omitted, disable diagnostics in all buffers. ----@param namespace integer|nil Only disable diagnostics for the given namespace. +---@param bufnr integer? Buffer number, or 0 for current buffer. When +--- omitted, disable diagnostics in all buffers. +---@param namespace integer? Only disable diagnostics for the given namespace. function M.disable(bufnr, namespace) vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } }) if bufnr == nil then @@ -1653,9 +1860,9 @@ end --- Enable diagnostics in the given buffer. --- ----@param bufnr integer|nil Buffer number, or 0 for current buffer. When ---- omitted, enable diagnostics in all buffers. ----@param namespace integer|nil Only enable diagnostics for the given namespace. +---@param bufnr integer? Buffer number, or 0 for current buffer. When +--- omitted, enable diagnostics in all buffers. +---@param namespace integer? Only enable diagnostics for the given namespace. function M.enable(bufnr, namespace) vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } }) if bufnr == nil then @@ -1701,14 +1908,14 @@ end --- ---@param str string String to parse diagnostics from. ---@param pat string Lua pattern with capture groups. ----@param groups table List of fields in a |diagnostic-structure| to +---@param groups string[] List of fields in a |diagnostic-structure| to --- associate with captures from {pat}. ---@param severity_map table A table mapping the severity field from {groups} --- with an item from |vim.diagnostic.severity|. ----@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|nil: |diagnostic-structure| or `nil` if {pat} fails to match {str}. +---@param defaults table? Table of default values for any fields not listed in {groups}. +--- When omitted, numeric values default to 0 and "severity" defaults to +--- ERROR. +---@return vim.Diagnostic?: |diagnostic-structure| or `nil` if {pat} fails to match {str}. function M.match(str, pat, groups, severity_map, defaults) vim.validate({ str = { str, 's' }, @@ -1718,14 +1925,16 @@ function M.match(str, pat, groups, severity_map, defaults) defaults = { defaults, 't', true }, }) + --- @type table<string,vim.diagnostic.Severity> severity_map = severity_map or M.severity - local diagnostic = {} - local matches = { string.match(str, pat) } + local matches = { str:match(pat) } --- @type any[] if vim.tbl_isempty(matches) then return end + local diagnostic = {} --- @type type<string,any> + for i, match in ipairs(matches) do local field = groups[i] if field == 'severity' then @@ -1733,10 +1942,10 @@ function M.match(str, pat, groups, severity_map, defaults) elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then match = assert(tonumber(match)) - 1 end - diagnostic[field] = match + diagnostic[field] = match --- @type any end - diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {}) + diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {}) --- @type vim.Diagnostic diagnostic.severity = diagnostic.severity or M.severity.ERROR diagnostic.col = diagnostic.col or 0 diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum @@ -1754,7 +1963,7 @@ local errlist_type_map = { --- Convert a list of diagnostics to a list of quickfix items that can be --- passed to |setqflist()| or |setloclist()|. --- ----@param diagnostics table List of diagnostics |diagnostic-structure|. +---@param diagnostics vim.Diagnostic[] List of diagnostics |diagnostic-structure|. ---@return table[] of quickfix list items |setqflist-what| function M.toqflist(diagnostics) vim.validate({ @@ -1765,7 +1974,7 @@ function M.toqflist(diagnostics) }, }) - local list = {} + local list = {} --- @type table[] for _, v in ipairs(diagnostics) do local item = { bufnr = v.bufnr, @@ -1795,7 +2004,7 @@ end --- Convert a list of quickfix items to a list of diagnostics. --- ---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|. ----@return Diagnostic[] array of |diagnostic-structure| +---@return vim.Diagnostic[] array of |diagnostic-structure| function M.fromqflist(list) vim.validate({ list = { @@ -1805,7 +2014,7 @@ function M.fromqflist(list) }, }) - local diagnostics = {} + local diagnostics = {} --- @type vim.Diagnostic[] for _, item in ipairs(list) do if item.valid == 1 then local lnum = math.max(0, item.lnum - 1) @@ -1813,7 +2022,7 @@ function M.fromqflist(list) local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum local end_col = item.end_col > 0 and (item.end_col - 1) or col local severity = item.type ~= '' and M.severity[item.type] or M.severity.ERROR - table.insert(diagnostics, { + diagnostics[#diagnostics + 1] = { bufnr = item.bufnr, lnum = lnum, col = col, @@ -1821,7 +2030,7 @@ function M.fromqflist(list) end_col = end_col, severity = severity, message = item.text, - }) + } end end return diagnostics |