2022-11-19 06:41:47 -07:00
local api , if_nil = vim.api , vim.F . if_nil
2021-11-14 18:40:11 -07:00
2021-09-06 19:21:18 -07:00
local M = { }
2024-01-09 05:47:57 -07:00
--- @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}
2024-01-22 23:04:20 -07:00
--- @field severity? vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @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
2024-01-22 23:04:20 -07:00
--- @field severity? vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @class vim.diagnostic.Opts.VirtualText
2024-01-22 23:04:20 -07:00
--- @field severity? vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @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
2024-01-22 23:04:20 -07:00
--- @field severity? vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @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
2021-09-06 19:21:18 -07:00
M.severity = {
ERROR = 1 ,
WARN = 2 ,
INFO = 3 ,
HINT = 4 ,
2024-01-09 05:47:57 -07:00
[ 1 ] = ' ERROR ' ,
[ 2 ] = ' WARN ' ,
[ 3 ] = ' INFO ' ,
[ 4 ] = ' HINT ' ,
2021-09-06 19:21:18 -07:00
}
2024-01-09 05:47:57 -07:00
--- @alias vim.diagnostic.SeverityInt 1|2|3|4
2021-09-06 19:21:18 -07:00
2024-01-22 23:04:20 -07:00
--- 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}
2021-09-19 15:13:23 -07:00
-- Mappings from qflist/loclist error types to severities
M.severity . E = M.severity . ERROR
M.severity . W = M.severity . WARN
M.severity . I = M.severity . INFO
M.severity . N = M.severity . HINT
2024-01-09 05:47:57 -07:00
--- @type vim.diagnostic.Opts
2021-09-06 19:21:18 -07:00
local global_diagnostic_options = {
signs = true ,
underline = true ,
virtual_text = true ,
2021-10-19 10:45:51 -07:00
float = true ,
2021-09-06 19:21:18 -07:00
update_in_insert = false ,
severity_sort = false ,
}
2024-01-09 05:47:57 -07:00
--- @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>
2021-10-29 18:47:34 -07:00
M.handlers = setmetatable ( { } , {
__newindex = function ( t , name , handler )
vim.validate ( { handler = { handler , ' t ' } } )
rawset ( t , name , handler )
2022-01-11 16:44:07 -07:00
if global_diagnostic_options [ name ] == nil then
2021-10-29 18:47:34 -07:00
global_diagnostic_options [ name ] = true
end
end ,
} )
2021-11-19 11:20:04 -07:00
-- Metatable that automatically creates an empty table when assigning to a missing key
local bufnr_and_namespace_cacher_mt = {
2024-01-09 05:47:57 -07:00
--- @param t table<integer,table>
--- @param bufnr integer
--- @return table
2021-11-19 11:20:04 -07:00
__index = function ( t , bufnr )
2021-11-22 08:47:30 -07:00
assert ( bufnr > 0 , ' Invalid buffer number ' )
t [ bufnr ] = { }
return t [ bufnr ]
2021-11-19 11:20:04 -07:00
end ,
}
2024-01-09 05:47:57 -07:00
-- bufnr -> ns -> Diagnostic[]
local diagnostic_cache = { } --- @type table<integer,table<integer,vim.Diagnostic[]>>
2022-08-29 10:09:14 -07:00
do
2022-11-19 06:41:47 -07:00
local group = api.nvim_create_augroup ( ' DiagnosticBufWipeout ' , { } )
2024-01-09 05:47:57 -07:00
setmetatable ( diagnostic_cache , {
--- @param t table<integer,vim.Diagnostic[]>
--- @param bufnr integer
2022-08-29 10:09:14 -07:00
__index = function ( t , bufnr )
assert ( bufnr > 0 , ' Invalid buffer number ' )
2022-11-19 06:41:47 -07:00
api.nvim_create_autocmd ( ' BufWipeout ' , {
2022-08-29 10:09:14 -07:00
group = group ,
buffer = bufnr ,
callback = function ( )
rawset ( t , bufnr , nil )
end ,
} )
t [ bufnr ] = { }
return t [ bufnr ]
end ,
} )
end
2021-11-21 18:40:06 -07:00
2024-01-09 05:47:57 -07:00
--- @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[]>>
2021-11-19 11:20:04 -07:00
local diagnostic_cache_extmarks = setmetatable ( { } , bufnr_and_namespace_cacher_mt )
2024-01-09 05:47:57 -07:00
--- @type table<integer,true>
2021-11-19 11:20:04 -07:00
local diagnostic_attached_buffers = { }
2024-01-09 05:47:57 -07:00
--- @type table<integer,true|table<integer,true>>
2021-11-19 11:20:04 -07:00
local diagnostic_disabled = { }
2024-01-09 05:47:57 -07:00
--- @type table<integer,table<integer,table>>
2021-11-19 11:20:04 -07:00
local bufs_waiting_to_update = setmetatable ( { } , bufnr_and_namespace_cacher_mt )
2024-01-09 05:47:57 -07:00
--- @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>
2021-11-19 11:20:04 -07:00
local all_namespaces = { }
2021-09-06 19:21:18 -07:00
2024-01-09 05:47:57 -07:00
---@param severity string|vim.diagnostic.Severity
---@return vim.diagnostic.Severity?
2021-09-06 19:21:18 -07:00
local function to_severity ( severity )
2021-10-08 11:28:02 -07:00
if type ( severity ) == ' string ' then
2024-01-17 12:34:25 -07:00
assert ( M.severity [ string.upper ( severity ) ] , string.format ( ' Invalid severity: %s ' , severity ) )
return M.severity [ string.upper ( severity ) ]
2021-10-08 11:28:02 -07:00
end
return severity
2021-09-06 19:21:18 -07:00
end
2024-01-22 23:04:20 -07:00
--- @param severity vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @param diagnostics vim.Diagnostic[]
--- @return vim.Diagnostic[]
2021-09-06 19:21:18 -07:00
local function filter_by_severity ( severity , diagnostics )
if not severity then
return diagnostics
end
if type ( severity ) ~= ' table ' then
2024-01-09 05:47:57 -07:00
severity = assert ( to_severity ( severity ) )
--- @param t vim.Diagnostic
2021-09-06 19:21:18 -07:00
return vim.tbl_filter ( function ( t )
return t.severity == severity
end , diagnostics )
end
2023-08-16 06:49:14 -07:00
if severity.min or severity.max then
2024-01-09 05:47:57 -07:00
--- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
2023-08-16 06:49:14 -07:00
local min_severity = to_severity ( severity.min ) or M.severity . HINT
local max_severity = to_severity ( severity.max ) or M.severity . ERROR
2024-01-09 05:47:57 -07:00
--- @param t vim.Diagnostic
2023-08-16 06:49:14 -07:00
return vim.tbl_filter ( function ( t )
return t.severity <= min_severity and t.severity >= max_severity
end , diagnostics )
end
2024-01-09 05:47:57 -07:00
--- @cast severity vim.diagnostic.Severity[]
local severities = { } --- @type table<vim.diagnostic.Severity,true>
2023-08-16 06:49:14 -07:00
for _ , s in ipairs ( severity ) do
2024-01-09 05:47:57 -07:00
severities [ assert ( to_severity ( s ) ) ] = true
2023-08-16 06:49:14 -07:00
end
2021-09-06 19:21:18 -07:00
2024-01-09 05:47:57 -07:00
--- @param t vim.Diagnostic
2021-09-06 19:21:18 -07:00
return vim.tbl_filter ( function ( t )
2023-08-16 06:49:14 -07:00
return severities [ t.severity ]
2021-09-06 19:21:18 -07:00
end , diagnostics )
end
2024-01-09 05:47:57 -07:00
--- @param bufnr integer
--- @return integer
2021-12-17 19:38:33 -07:00
local function count_sources ( bufnr )
2024-01-09 05:47:57 -07:00
local seen = { } --- @type table<string,true>
2021-12-17 19:38:33 -07:00
local count = 0
for _ , namespace_diagnostics in pairs ( diagnostic_cache [ bufnr ] ) do
for _ , diagnostic in ipairs ( namespace_diagnostics ) do
2024-01-09 05:47:57 -07:00
local source = diagnostic.source
if source and not seen [ source ] then
seen [ source ] = true
2021-12-17 19:38:33 -07:00
count = count + 1
2021-09-18 14:00:32 -07:00
end
end
end
2021-12-17 19:38:33 -07:00
return count
end
2021-09-18 14:00:32 -07:00
2024-01-09 05:47:57 -07:00
--- @param diagnostics vim.Diagnostic[]
--- @return vim.Diagnostic[]
2021-12-17 19:38:33 -07:00
local function prefix_source ( diagnostics )
2024-01-09 05:47:57 -07:00
--- @param d vim.Diagnostic
2021-09-18 14:00:32 -07:00
return vim.tbl_map ( function ( d )
if not d.source then
return d
end
2024-01-02 08:47:55 -07:00
local t = vim.deepcopy ( d , true )
2021-09-18 14:00:32 -07:00
t.message = string.format ( ' %s: %s ' , d.source , d.message )
return t
end , diagnostics )
end
2024-01-09 05:47:57 -07:00
--- @param diagnostics vim.Diagnostic[]
--- @return vim.Diagnostic[]
2021-09-22 12:20:15 -07:00
local function reformat_diagnostics ( format , diagnostics )
vim.validate ( {
format = { format , ' f ' } ,
diagnostics = { diagnostics , ' t ' } ,
} )
2024-01-02 08:47:55 -07:00
local formatted = vim.deepcopy ( diagnostics , true )
2021-09-22 12:20:15 -07:00
for _ , diagnostic in ipairs ( formatted ) do
diagnostic.message = format ( diagnostic )
end
return formatted
end
2024-01-09 05:47:57 -07:00
--- @param option string
--- @param namespace integer?
--- @return table
2021-10-17 07:18:35 -07:00
local function enabled_value ( option , namespace )
2021-10-29 18:47:34 -07:00
local ns = namespace and M.get_namespace ( namespace ) or { }
2021-10-19 10:45:51 -07:00
if ns.opts and type ( ns.opts [ option ] ) == ' table ' then
2021-10-17 07:18:35 -07:00
return ns.opts [ option ]
end
2024-01-09 05:47:57 -07:00
local global_opt = global_diagnostic_options [ option ]
if type ( global_opt ) == ' table ' then
return global_opt
2021-10-17 07:18:35 -07:00
end
return { }
end
2024-01-09 05:47:57 -07:00
--- @param option string
--- @param value any?
--- @param namespace integer?
--- @param bufnr integer
--- @return any
2021-10-17 07:18:35 -07:00
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
2024-01-09 05:47:57 -07:00
local val = value ( namespace , bufnr ) --- @type any
2021-10-17 07:18:35 -07:00
if val == true then
return enabled_value ( option , namespace )
else
return val
end
elseif type ( value ) == ' table ' then
return value
end
2024-01-09 05:47:57 -07:00
error ( ' Unexpected option type: ' .. vim.inspect ( value ) )
2021-10-17 07:18:35 -07:00
end
2024-01-09 05:47:57 -07:00
--- @param opts vim.diagnostic.Opts?
--- @param namespace integer?
--- @param bufnr integer
--- @return vim.diagnostic.OptsResolved
2021-09-06 19:21:18 -07:00
local function get_resolved_options ( opts , namespace , bufnr )
2021-10-29 18:47:34 -07:00
local ns = namespace and M.get_namespace ( namespace ) or { }
2021-10-19 10:45:51 -07:00
-- Do not use tbl_deep_extend so that an empty table can be used to reset to default values
2024-01-09 05:47:57 -07:00
local resolved = vim.tbl_extend ( ' keep ' , opts or { } , ns.opts or { } , global_diagnostic_options ) --- @type table<string,any>
2021-09-06 19:21:18 -07:00
for k in pairs ( global_diagnostic_options ) do
if resolved [ k ] ~= nil then
2021-10-17 07:18:35 -07:00
resolved [ k ] = resolve_optional_value ( k , resolved [ k ] , namespace , bufnr )
2021-09-06 19:21:18 -07:00
end
end
return resolved
end
-- Default diagnostic highlights
local diagnostic_severities = {
[ M.severity . ERROR ] = { ctermfg = 1 , guifg = ' Red ' } ,
[ M.severity . WARN ] = { ctermfg = 3 , guifg = ' Orange ' } ,
[ M.severity . INFO ] = { ctermfg = 4 , guifg = ' LightBlue ' } ,
[ M.severity . HINT ] = { ctermfg = 7 , guifg = ' LightGrey ' } ,
}
2024-01-09 05:47:57 -07:00
--- Make a map from vim.diagnostic.Severity -> Highlight Name
--- @param base_name string
--- @return table<vim.diagnostic.SeverityInt,string>
2021-09-06 19:21:18 -07:00
local function make_highlight_map ( base_name )
2024-01-09 05:47:57 -07:00
local result = { } --- @type table<vim.diagnostic.SeverityInt,string>
2021-09-06 19:21:18 -07:00
for k in pairs ( diagnostic_severities ) do
local name = M.severity [ k ]
name = name : sub ( 1 , 1 ) .. name : sub ( 2 ) : lower ( )
result [ k ] = ' Diagnostic ' .. base_name .. name
end
return result
end
2024-01-09 05:47:57 -07:00
-- 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
2021-09-06 19:21:18 -07:00
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 ' )
local sign_highlight_map = make_highlight_map ( ' Sign ' )
local function get_bufnr ( bufnr )
if not bufnr or bufnr == 0 then
2022-11-19 06:41:47 -07:00
return api.nvim_get_current_buf ( )
2021-09-06 19:21:18 -07:00
end
return bufnr
end
2024-01-09 05:47:57 -07:00
--- @param diagnostics vim.Diagnostic[]
--- @return table<integer,vim.Diagnostic[]>
2021-09-06 19:21:18 -07:00
local function diagnostic_lines ( diagnostics )
if not diagnostics then
2021-09-23 08:23:57 -07:00
return { }
2021-09-06 19:21:18 -07:00
end
2024-01-09 05:47:57 -07:00
local diagnostics_by_line = { } --- @type table<integer,vim.Diagnostic[]>
2021-09-06 19:21:18 -07:00
for _ , diagnostic in ipairs ( diagnostics ) do
local line_diagnostics = diagnostics_by_line [ diagnostic.lnum ]
if not line_diagnostics then
line_diagnostics = { }
diagnostics_by_line [ diagnostic.lnum ] = line_diagnostics
end
table.insert ( line_diagnostics , diagnostic )
end
return diagnostics_by_line
end
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr integer
--- @param diagnostics vim.Diagnostic[]
2021-09-20 11:32:21 -07:00
local function set_diagnostic_cache ( namespace , bufnr , diagnostics )
2021-09-06 19:21:18 -07:00
for _ , diagnostic in ipairs ( diagnostics ) do
2021-12-16 11:20:18 -07:00
assert ( diagnostic.lnum , ' Diagnostic line number is required ' )
assert ( diagnostic.col , ' Diagnostic column is required ' )
2021-09-20 11:32:21 -07:00
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
2021-09-06 19:21:18 -07:00
diagnostic.namespace = namespace
diagnostic.bufnr = bufnr
end
diagnostic_cache [ bufnr ] [ namespace ] = diagnostics
end
2024-01-09 05:47:57 -07:00
--- @param bufnr integer
--- @param last integer
2021-09-06 19:21:18 -07:00
local function restore_extmarks ( bufnr , last )
for ns , extmarks in pairs ( diagnostic_cache_extmarks [ bufnr ] ) do
2022-11-19 06:41:47 -07:00
local extmarks_current = api.nvim_buf_get_extmarks ( bufnr , ns , 0 , - 1 , { details = true } )
2024-01-09 05:47:57 -07:00
local found = { } --- @type table<integer,true>
2021-09-06 19:21:18 -07:00
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
if extmark [ 2 ] ~= last + 1 then
found [ extmark [ 1 ] ] = true
end
end
for _ , extmark in ipairs ( extmarks ) do
if not found [ extmark [ 1 ] ] then
local opts = extmark [ 4 ]
opts.id = extmark [ 1 ]
2022-11-19 06:41:47 -07:00
pcall ( api.nvim_buf_set_extmark , bufnr , ns , extmark [ 2 ] , extmark [ 3 ] , opts )
2021-09-06 19:21:18 -07:00
end
end
end
end
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr? integer
2021-09-06 19:21:18 -07:00
local function save_extmarks ( namespace , bufnr )
2021-11-09 14:33:01 -07:00
bufnr = get_bufnr ( bufnr )
2021-09-06 19:21:18 -07:00
if not diagnostic_attached_buffers [ bufnr ] then
2022-11-19 06:41:47 -07:00
api.nvim_buf_attach ( bufnr , false , {
2021-09-06 19:21:18 -07:00
on_lines = function ( _ , _ , _ , _ , _ , last )
restore_extmarks ( bufnr , last - 1 )
end ,
on_detach = function ( )
diagnostic_cache_extmarks [ bufnr ] = nil
end ,
} )
diagnostic_attached_buffers [ bufnr ] = true
end
diagnostic_cache_extmarks [ bufnr ] [ namespace ] =
2022-11-19 06:41:47 -07:00
api.nvim_buf_get_extmarks ( bufnr , namespace , 0 , - 1 , { details = true } )
2021-09-06 19:21:18 -07:00
end
2024-01-09 05:47:57 -07:00
--- @type table<string,true>
2021-09-06 19:21:18 -07:00
local registered_autocmds = { }
local function make_augroup_key ( namespace , bufnr )
2021-10-29 18:47:34 -07:00
local ns = M.get_namespace ( namespace )
2021-09-06 19:21:18 -07:00
return string.format ( ' DiagnosticInsertLeave:%s:%s ' , bufnr , ns.name )
end
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr integer
2022-07-09 09:40:32 -07:00
local function execute_scheduled_display ( namespace , bufnr )
local args = bufs_waiting_to_update [ bufnr ] [ namespace ]
if not args then
return
end
-- Clear the args so we don't display unnecessarily.
bufs_waiting_to_update [ bufnr ] [ namespace ] = nil
M.show ( namespace , bufnr , nil , args )
end
2021-09-06 19:21:18 -07:00
--- Table of autocmd events to fire the update for displaying new diagnostic information
local insert_leave_auto_cmds = { ' InsertLeave ' , ' CursorHoldI ' }
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr integer
--- @param args any[]
2021-09-06 19:21:18 -07:00
local function schedule_display ( namespace , bufnr , args )
bufs_waiting_to_update [ bufnr ] [ namespace ] = args
local key = make_augroup_key ( namespace , bufnr )
if not registered_autocmds [ key ] then
2022-11-19 06:41:47 -07:00
local group = api.nvim_create_augroup ( key , { clear = true } )
api.nvim_create_autocmd ( insert_leave_auto_cmds , {
2022-07-09 09:40:32 -07:00
group = group ,
buffer = bufnr ,
callback = function ( )
execute_scheduled_display ( namespace , bufnr )
end ,
2022-07-17 10:11:05 -07:00
desc = ' vim.diagnostic: display diagnostics ' ,
2022-07-09 09:40:32 -07:00
} )
2021-09-06 19:21:18 -07:00
registered_autocmds [ key ] = true
end
end
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr integer
2021-09-06 19:21:18 -07:00
local function clear_scheduled_display ( namespace , bufnr )
local key = make_augroup_key ( namespace , bufnr )
if registered_autocmds [ key ] then
2022-11-19 06:41:47 -07:00
api.nvim_del_augroup_by_name ( key )
2021-09-06 19:21:18 -07:00
registered_autocmds [ key ] = nil
end
end
2024-01-09 05:47:57 -07:00
--- @param bufnr integer?
--- @param opts vim.diagnostic.GetOpts?
--- @param clamp boolean
--- @return vim.Diagnostic[]
2021-11-19 11:20:04 -07:00
local function get_diagnostics ( bufnr , opts , clamp )
opts = opts or { }
local namespace = opts.namespace
local diagnostics = { }
2021-11-24 19:32:26 -07:00
-- Memoized results of buf_line_count per bufnr
2024-01-09 05:47:57 -07:00
--- @type table<integer,integer>
2021-11-24 19:32:26 -07:00
local buf_line_count = setmetatable ( { } , {
2024-01-09 05:47:57 -07:00
--- @param t table<integer,integer>
--- @param k integer
--- @return integer
2021-11-24 19:32:26 -07:00
__index = function ( t , k )
2022-11-19 06:41:47 -07:00
t [ k ] = api.nvim_buf_line_count ( k )
2021-11-24 19:32:26 -07:00
return rawget ( t , k )
end ,
} )
2021-11-19 11:20:04 -07:00
2024-01-09 05:47:57 -07:00
---@param b integer
---@param d vim.Diagnostic
2021-11-24 19:32:26 -07:00
local function add ( b , d )
2021-11-19 11:20:04 -07:00
if not opts.lnum or d.lnum == opts.lnum then
2022-11-19 06:41:47 -07:00
if clamp and api.nvim_buf_is_loaded ( b ) then
2021-11-24 19:32:26 -07:00
local line_count = buf_line_count [ b ] - 1
2022-06-08 11:55:39 -07:00
if
d.lnum > line_count
or d.end_lnum > line_count
or d.lnum < 0
or d.end_lnum < 0
or d.col < 0
or d.end_col < 0
then
2024-01-02 08:47:55 -07:00
d = vim.deepcopy ( d , true )
2021-11-24 19:32:26 -07:00
d.lnum = math.max ( math.min ( d.lnum , line_count ) , 0 )
2024-01-09 05:47:57 -07:00
d.end_lnum = math.max ( math.min ( assert ( d.end_lnum ) , line_count ) , 0 )
2022-06-08 11:55:39 -07:00
d.col = math.max ( d.col , 0 )
d.end_col = math.max ( d.end_col , 0 )
2021-11-24 19:32:26 -07:00
end
2021-11-19 11:20:04 -07:00
end
table.insert ( diagnostics , d )
end
2021-09-20 11:32:21 -07:00
end
2024-01-09 05:47:57 -07:00
--- @param buf integer
--- @param diags vim.Diagnostic[]
2023-01-03 03:07:27 -07:00
local function add_all_diags ( buf , diags )
for _ , diagnostic in pairs ( diags ) do
add ( buf , diagnostic )
end
end
2021-11-19 11:20:04 -07:00
if namespace == nil and bufnr == nil then
2021-11-24 19:32:26 -07:00
for b , t in pairs ( diagnostic_cache ) do
2021-11-19 11:20:04 -07:00
for _ , v in pairs ( t ) do
2023-01-03 03:07:27 -07:00
add_all_diags ( b , v )
2021-11-19 11:20:04 -07:00
end
end
elseif namespace == nil then
2021-11-22 09:22:08 -07:00
bufnr = get_bufnr ( bufnr )
2021-11-19 11:20:04 -07:00
for iter_namespace in pairs ( diagnostic_cache [ bufnr ] ) do
2023-01-03 03:07:27 -07:00
add_all_diags ( bufnr , diagnostic_cache [ bufnr ] [ iter_namespace ] )
2021-11-19 11:20:04 -07:00
end
elseif bufnr == nil then
2021-11-24 19:32:26 -07:00
for b , t in pairs ( diagnostic_cache ) do
2023-01-03 03:07:27 -07:00
add_all_diags ( b , t [ namespace ] or { } )
2021-11-19 11:20:04 -07:00
end
else
2021-11-22 09:22:08 -07:00
bufnr = get_bufnr ( bufnr )
2023-01-03 03:07:27 -07:00
add_all_diags ( bufnr , diagnostic_cache [ bufnr ] [ namespace ] or { } )
2021-11-19 11:20:04 -07:00
end
if opts.severity then
diagnostics = filter_by_severity ( opts.severity , diagnostics )
2021-09-20 11:32:21 -07:00
end
2021-11-19 11:20:04 -07:00
return diagnostics
2021-09-20 11:32:21 -07:00
end
2024-01-09 05:47:57 -07:00
--- @param loclist boolean
--- @param opts vim.diagnostic.setqflist.Opts|vim.diagnostic.setloclist.Opts?
2021-11-19 11:49:44 -07:00
local function set_list ( loclist , opts )
opts = opts or { }
2024-01-09 05:47:57 -07:00
local open = if_nil ( opts.open , true )
2021-11-19 11:49:44 -07:00
local title = opts.title or ' Diagnostics '
local winnr = opts.winnr or 0
2024-01-09 05:47:57 -07:00
local bufnr --- @type integer?
2021-11-19 11:49:44 -07:00
if loclist then
2022-11-19 06:41:47 -07:00
bufnr = api.nvim_win_get_buf ( winnr )
2021-11-19 11:49:44 -07:00
end
2021-11-24 19:57:27 -07:00
-- Don't clamp line numbers since the quickfix list can already handle line
-- numbers beyond the end of the buffer
2024-01-09 05:47:57 -07:00
local diagnostics = get_diagnostics ( bufnr , opts --[[@as vim.diagnostic.GetOpts]] , false )
2021-11-19 11:49:44 -07:00
local items = M.toqflist ( diagnostics )
if loclist then
vim.fn . setloclist ( winnr , { } , ' ' , { title = title , items = items } )
else
vim.fn . setqflist ( { } , ' ' , { title = title , items = items } )
end
if open then
2022-12-30 11:23:54 -07:00
api.nvim_command ( loclist and ' lwindow ' or ' botright cwindow ' )
2021-11-19 11:49:44 -07:00
end
end
2024-01-09 05:47:57 -07:00
--- @param position {[1]: integer, [2]: integer}
--- @param search_forward boolean
--- @param bufnr integer
--- @param opts vim.diagnostic.GotoOpts
--- @param namespace integer
--- @return vim.Diagnostic?
2021-09-17 18:57:14 -07:00
local function next_diagnostic ( position , search_forward , bufnr , opts , namespace )
position [ 1 ] = position [ 1 ] - 1
2021-09-20 11:32:21 -07:00
bufnr = get_bufnr ( bufnr )
2024-01-09 05:47:57 -07:00
local wrap = if_nil ( opts.wrap , true )
2022-11-19 06:41:47 -07:00
local line_count = api.nvim_buf_line_count ( bufnr )
2021-11-19 11:20:04 -07:00
local diagnostics =
get_diagnostics ( bufnr , vim.tbl_extend ( ' keep ' , opts , { namespace = namespace } ) , true )
2021-09-20 11:32:21 -07:00
local line_diagnostics = diagnostic_lines ( diagnostics )
2023-03-30 06:49:58 -07:00
2021-09-17 18:57:14 -07:00
for i = 0 , line_count do
local offset = i * ( search_forward and 1 or - 1 )
local lnum = position [ 1 ] + offset
if lnum < 0 or lnum >= line_count then
if not wrap then
return
end
lnum = ( lnum + line_count ) % line_count
end
2021-09-20 11:32:21 -07:00
if line_diagnostics [ lnum ] and not vim.tbl_isempty ( line_diagnostics [ lnum ] ) then
2022-11-19 06:41:47 -07:00
local line_length = # api.nvim_buf_get_lines ( bufnr , lnum , lnum + 1 , true ) [ 1 ]
2024-01-09 05:47:57 -07:00
--- @type function, function
2021-09-17 18:57:14 -07:00
local sort_diagnostics , is_next
if search_forward then
sort_diagnostics = function ( a , b )
return a.col < b.col
end
2021-11-19 11:31:33 -07:00
is_next = function ( d )
return math.min ( d.col , line_length - 1 ) > position [ 2 ]
end
2021-09-17 18:57:14 -07:00
else
sort_diagnostics = function ( a , b )
return a.col > b.col
end
2021-11-19 11:31:33 -07:00
is_next = function ( d )
return math.min ( d.col , line_length - 1 ) < position [ 2 ]
end
2021-09-17 18:57:14 -07:00
end
2021-09-20 11:32:21 -07:00
table.sort ( line_diagnostics [ lnum ] , sort_diagnostics )
2021-09-17 18:57:14 -07:00
if i == 0 then
2024-01-09 05:47:57 -07:00
for _ , v in
pairs ( line_diagnostics [ lnum ] --[[@as table<string,any>]] )
do
2021-09-17 18:57:14 -07:00
if is_next ( v ) then
return v
end
end
else
2021-09-20 11:32:21 -07:00
return line_diagnostics [ lnum ] [ 1 ]
2021-09-17 18:57:14 -07:00
end
end
end
end
2024-01-09 05:47:57 -07:00
--- @param opts vim.diagnostic.GotoOpts?
--- @param pos {[1]:integer,[2]:integer}|false
2021-09-17 18:57:14 -07:00
local function diagnostic_move_pos ( opts , pos )
opts = opts or { }
2024-01-09 05:47:57 -07:00
local float = if_nil ( opts.float , true )
2022-11-19 06:41:47 -07:00
local win_id = opts.win_id or api.nvim_get_current_win ( )
2021-09-17 18:57:14 -07:00
if not pos then
2022-11-19 06:41:47 -07:00
api.nvim_echo ( { { ' No more valid diagnostics to move to ' , ' WarningMsg ' } } , true , { } )
2021-09-17 18:57:14 -07:00
return
end
2022-11-19 06:41:47 -07:00
api.nvim_win_call ( win_id , function ( )
2021-12-04 14:14:38 -07:00
-- Save position in the window's jumplist
vim.cmd ( " normal! m' " )
2022-11-19 06:41:47 -07:00
api.nvim_win_set_cursor ( win_id , { pos [ 1 ] + 1 , pos [ 2 ] } )
2021-12-04 14:14:38 -07:00
-- Open folds under the cursor
vim.cmd ( ' normal! zv ' )
end )
2021-09-17 18:57:14 -07:00
2021-10-19 10:45:51 -07:00
if float then
local float_opts = type ( float ) == ' table ' and float or { }
2021-09-17 18:57:14 -07:00
vim.schedule ( function ( )
2021-11-24 21:45:42 -07:00
M.open_float ( vim.tbl_extend ( ' keep ' , float_opts , {
2022-11-19 06:41:47 -07:00
bufnr = api.nvim_win_get_buf ( win_id ) ,
2021-11-24 21:45:42 -07:00
scope = ' cursor ' ,
2021-11-29 15:37:55 -07:00
focus = false ,
2021-11-24 21:45:42 -07:00
} ) )
2021-09-17 18:57:14 -07:00
end )
end
end
2021-09-06 19:21:18 -07:00
--- Configure diagnostic options globally or for a specific diagnostic
--- namespace.
---
2021-10-17 07:18:35 -07:00
--- Configuration can be specified globally, per-namespace, or ephemerally
--- (i.e. only for a single call to |vim.diagnostic.set()| or
--- |vim.diagnostic.show()|). Ephemeral configuration has highest priority,
--- followed by namespace configuration, and finally global configuration.
---
--- For example, if a user enables virtual text globally with
2023-09-14 06:23:01 -07:00
---
--- ```lua
--- vim.diagnostic.config({ virtual_text = true })
--- ```
2021-10-17 07:18:35 -07:00
---
--- and a diagnostic producer sets diagnostics with
2023-09-14 06:23:01 -07:00
---
--- ```lua
--- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
--- ```
2021-10-17 07:18:35 -07:00
---
--- then virtual text will not be enabled for those diagnostics.
---
2021-09-06 19:21:18 -07:00
---@note Each of the configuration options below accepts one of the following:
--- - `false`: Disable this feature
--- - `true`: Enable this feature, use default settings.
2021-10-17 07:18:35 -07:00
--- - `table`: Enable this feature with overrides. Use an empty table to use default values.
2021-09-06 19:21:18 -07:00
--- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
---
2024-01-09 05:47:57 -07:00
---@param opts vim.diagnostic.Opts? (table?) When omitted or "nil", retrieve the current
--- configuration. Otherwise, a configuration table with the following keys:
2021-09-17 09:50:25 -07:00
--- - underline: (default true) Use underline for diagnostics. Options:
2023-08-09 02:06:13 -07:00
--- * severity: Only underline diagnostics matching the given
--- severity |diagnostic-severity|
2021-09-17 09:50:25 -07:00
--- - virtual_text: (default true) Use virtual text for diagnostics. If multiple diagnostics
2021-10-01 22:50:28 -07:00
--- are set for a namespace, one prefix per diagnostic + the last diagnostic
2023-08-16 06:21:32 -07:00
--- message are shown. In addition to the options listed below, the
--- "virt_text" options of |nvim_buf_set_extmark()| may also be used here
--- (e.g. "virt_text_pos" and "hl_mode").
2021-09-17 09:50:25 -07:00
--- Options:
--- * severity: Only show virtual text for diagnostics matching the given
--- severity |diagnostic-severity|
2021-12-17 19:38:33 -07:00
--- * source: (boolean or string) Include the diagnostic source in virtual
--- text. Use "if_many" to only show sources if there is more than
--- one diagnostic source in the buffer. Otherwise, any truthy value
--- means to always show the diagnostic source.
2021-09-06 19:21:18 -07:00
--- * spacing: (number) Amount of empty spaces inserted at the beginning
2021-09-17 09:50:25 -07:00
--- of the virtual text.
2023-04-17 04:53:34 -07:00
--- * prefix: (string or function) prepend diagnostic message with prefix.
2023-10-27 06:17:46 -07:00
--- If a function, it must have the signature (diagnostic, i, total)
--- -> string, where {diagnostic} is of type |diagnostic-structure|,
--- {i} is the index of the diagnostic being evaluated, and {total}
--- is the total number of diagnostics for the line. This can be
2023-04-17 04:53:34 -07:00
--- used to render diagnostic symbols or error codes.
2022-11-20 16:57:36 -07:00
--- * suffix: (string or function) Append diagnostic message with suffix.
--- If a function, it must have the signature (diagnostic) ->
--- string, where {diagnostic} is of type |diagnostic-structure|.
--- This can be used to render an LSP diagnostic error code.
2021-09-22 12:20:15 -07:00
--- * format: (function) A function that takes a diagnostic as input and
--- returns a string. The return value is the text used to display
--- the diagnostic. Example:
2022-11-23 04:31:49 -07:00
--- <pre>lua
--- function(diagnostic)
--- if diagnostic.severity == vim.diagnostic.severity.ERROR then
--- return string.format("E: %s", diagnostic.message)
--- end
--- return diagnostic.message
2021-09-22 12:20:15 -07:00
--- end
--- </pre>
2023-12-18 10:04:44 -07:00
--- - signs: (default true) Use signs for diagnostics |diagnostic-signs|. Options:
2023-08-09 02:06:13 -07:00
--- * severity: Only show signs for diagnostics matching the given
--- severity |diagnostic-severity|
2021-10-01 18:07:55 -07:00
--- * priority: (number, default 10) Base priority to use for signs. When
--- {severity_sort} is used, the priority of a sign is adjusted based on
--- its severity. Otherwise, all signs use the same priority.
2023-12-13 07:19:53 -07:00
--- * text: (table) A table mapping |diagnostic-severity| to the sign text
--- to display in the sign column. The default is to use "E", "W", "I", and "H"
--- for errors, warnings, information, and hints, respectively. Example:
--- <pre>lua
2023-12-13 08:43:27 -07:00
--- vim.diagnostic.config({
--- signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
--- })
2023-12-13 07:19:53 -07:00
--- </pre>
2023-12-13 08:54:04 -07:00
--- * numhl: (table) A table mapping |diagnostic-severity| to the highlight
--- group used for the line number where the sign is placed.
--- * linehl: (table) A table mapping |diagnostic-severity| to the highlight group
--- used for the whole line the sign is placed in.
2021-10-19 10:45:51 -07:00
--- - float: Options for floating windows. See |vim.diagnostic.open_float()|.
2021-09-06 19:21:18 -07:00
--- - update_in_insert: (default false) Update diagnostics in Insert mode (if false,
--- diagnostics are updated on InsertLeave)
--- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in
2021-09-17 13:59:30 -07:00
--- which signs and virtual text are displayed. When true, higher severities
--- are displayed before lower severities (e.g. ERROR is displayed before WARN).
--- Options:
2021-09-17 09:50:25 -07:00
--- * reverse: (boolean) Reverse sort order
2021-11-27 09:10:48 -07:00
---
2024-01-09 05:47:57 -07:00
---@param namespace integer? Update the options for the given namespace. When omitted, update the
--- global diagnostic options.
2023-12-17 17:11:47 -07:00
---
2024-01-09 05:47:57 -07:00
---@return vim.diagnostic.Opts? (table?) table of current diagnostic config if `opts` is omitted.
2021-09-06 19:21:18 -07:00
function M . config ( opts , namespace )
vim.validate ( {
2022-01-11 16:44:31 -07:00
opts = { opts , ' t ' , true } ,
2021-09-06 19:21:18 -07:00
namespace = { namespace , ' n ' , true } ,
} )
2024-01-09 05:47:57 -07:00
local t --- @type vim.diagnostic.Opts
2021-09-06 19:21:18 -07:00
if namespace then
2021-10-29 18:47:34 -07:00
local ns = M.get_namespace ( namespace )
2021-09-06 19:21:18 -07:00
t = ns.opts
else
t = global_diagnostic_options
end
2022-01-11 16:44:31 -07:00
if not opts then
-- Return current config
2024-01-02 08:47:55 -07:00
return vim.deepcopy ( t , true )
2022-01-11 16:44:31 -07:00
end
2024-01-09 05:47:57 -07:00
for k , v in
pairs ( opts --[[@as table<any,any>]] )
do
2022-01-11 16:43:47 -07:00
t [ k ] = v
2021-09-06 19:21:18 -07:00
end
if namespace then
for bufnr , v in pairs ( diagnostic_cache ) do
2023-10-16 18:51:36 -07:00
if v [ namespace ] then
2021-09-06 19:21:18 -07:00
M.show ( namespace , bufnr )
end
end
else
for bufnr , v in pairs ( diagnostic_cache ) do
2023-10-16 18:51:36 -07:00
for ns in pairs ( v ) do
M.show ( ns , bufnr )
2021-09-06 19:21:18 -07:00
end
end
end
end
--- Set diagnostics for the given namespace and buffer.
---
2023-03-04 06:06:20 -07:00
---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number
2024-01-09 05:47:57 -07:00
---@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()|
2021-09-06 19:21:18 -07:00
function M . set ( namespace , bufnr , diagnostics , opts )
vim.validate ( {
namespace = { namespace , ' n ' } ,
bufnr = { bufnr , ' n ' } ,
2022-01-01 12:58:34 -07:00
diagnostics = {
diagnostics ,
vim.tbl_islist ,
' a list of diagnostics ' ,
} ,
2021-09-06 19:21:18 -07:00
opts = { opts , ' t ' , true } ,
} )
2021-11-22 08:47:30 -07:00
bufnr = get_bufnr ( bufnr )
2021-09-06 19:21:18 -07:00
if vim.tbl_isempty ( diagnostics ) then
2021-11-21 18:40:06 -07:00
diagnostic_cache [ bufnr ] [ namespace ] = nil
2021-09-27 07:57:53 -07:00
else
set_diagnostic_cache ( namespace , bufnr , diagnostics )
2021-09-06 19:21:18 -07:00
end
2023-10-16 18:51:36 -07:00
M.show ( namespace , bufnr , nil , opts )
2021-09-06 19:21:18 -07:00
2022-11-19 06:41:47 -07:00
api.nvim_exec_autocmds ( ' DiagnosticChanged ' , {
2022-04-20 07:16:47 -07:00
modeline = false ,
buffer = bufnr ,
2024-01-09 05:47:57 -07:00
-- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get()
2022-09-13 07:33:39 -07:00
data = { diagnostics = diagnostics } ,
2022-04-20 07:16:47 -07:00
} )
2021-09-06 19:21:18 -07:00
end
2021-10-29 18:47:34 -07:00
--- Get namespace metadata.
---
2023-03-04 06:06:20 -07:00
---@param namespace integer Diagnostic namespace
2024-01-09 05:47:57 -07:00
---@return vim.diagnostic.NS (table) Namespace metadata
2021-10-29 18:47:34 -07:00
function M . get_namespace ( namespace )
vim.validate ( { namespace = { namespace , ' n ' } } )
if not all_namespaces [ namespace ] then
2024-01-09 05:47:57 -07:00
local name --- @type string?
2022-11-19 06:41:47 -07:00
for k , v in pairs ( api.nvim_get_namespaces ( ) ) do
2021-10-29 18:47:34 -07:00
if namespace == v then
name = k
break
end
end
assert ( name , ' namespace does not exist or is anonymous ' )
all_namespaces [ namespace ] = {
name = name ,
opts = { } ,
user_data = { } ,
}
end
return all_namespaces [ namespace ]
end
2021-10-01 22:50:28 -07:00
--- Get current diagnostic namespaces.
---
2024-01-09 05:47:57 -07:00
---@return table<integer,vim.diagnostic.NS> A list of active diagnostic namespaces |vim.diagnostic|.
2021-10-01 22:50:28 -07:00
function M . get_namespaces ( )
2024-01-02 08:47:55 -07:00
return vim.deepcopy ( all_namespaces , true )
2021-10-01 22:50:28 -07:00
end
2021-09-06 19:21:18 -07:00
--- Get current diagnostics.
---
2023-09-06 10:54:18 -07:00
--- Modifying diagnostics in the returned table has no effect. To set diagnostics in a buffer, use |vim.diagnostic.set()|.
---
2024-01-09 05:47:57 -07:00
---@param bufnr integer? Buffer number to get diagnostics from. Use 0 for
2021-09-06 19:21:18 -07:00
--- current buffer or nil for all buffers.
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.GetOpts (table) A table with the following keys:
2021-09-06 19:21:18 -07:00
--- - namespace: (number) Limit diagnostics to the given namespace.
--- - lnum: (number) Limit diagnostics to the given line number.
--- - severity: See |diagnostic-severity|.
2024-01-09 05:47:57 -07:00
---@return vim.Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present.
2021-09-06 19:21:18 -07:00
function M . get ( bufnr , opts )
vim.validate ( {
bufnr = { bufnr , ' n ' , true } ,
opts = { opts , ' t ' , true } ,
} )
2024-01-02 08:47:55 -07:00
return vim.deepcopy ( get_diagnostics ( bufnr , opts , false ) , true )
2024-01-01 14:03:50 -07:00
end
--- Get current diagnostics count.
---
2024-01-09 05:47:57 -07:00
---@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|.
2024-01-01 14:03:50 -07:00
---@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 ( {
bufnr = { bufnr , ' n ' , true } ,
opts = { opts , ' t ' , true } ,
} )
local diagnostics = get_diagnostics ( bufnr , opts , false )
2024-01-09 05:47:57 -07:00
local count = { } --- @type table<integer,integer>
2024-01-01 14:03:50 -07:00
for i = 1 , # diagnostics do
2024-01-09 05:47:57 -07:00
local severity = diagnostics [ i ] . severity --[[@as integer]]
2024-01-01 14:03:50 -07:00
count [ severity ] = ( count [ severity ] or 0 ) + 1
end
return count
2021-09-06 19:21:18 -07:00
end
--- Get the previous diagnostic closest to the cursor position.
---
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
---@return vim.Diagnostic? Previous diagnostic
2021-09-06 19:21:18 -07:00
function M . get_prev ( opts )
opts = opts or { }
2022-11-19 06:41:47 -07:00
local win_id = opts.win_id or api.nvim_get_current_win ( )
local bufnr = api.nvim_win_get_buf ( win_id )
local cursor_position = opts.cursor_position or api.nvim_win_get_cursor ( win_id )
2021-09-06 19:21:18 -07:00
return next_diagnostic ( cursor_position , false , bufnr , opts , opts.namespace )
end
--- Return the position of the previous diagnostic in the current buffer.
---
2024-01-09 05:47:57 -07:00
---@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
2021-09-06 19:21:18 -07:00
function M . get_prev_pos ( opts )
2021-09-16 10:26:45 -07:00
local prev = M.get_prev ( opts )
if not prev then
return false
end
return { prev.lnum , prev.col }
2021-09-06 19:21:18 -07:00
end
--- Move to the previous diagnostic in the current buffer.
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
2021-09-06 19:21:18 -07:00
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.
---
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
---@return vim.Diagnostic? : Next diagnostic
2021-09-06 19:21:18 -07:00
function M . get_next ( opts )
opts = opts or { }
2022-11-19 06:41:47 -07:00
local win_id = opts.win_id or api.nvim_get_current_win ( )
local bufnr = api.nvim_win_get_buf ( win_id )
local cursor_position = opts.cursor_position or api.nvim_win_get_cursor ( win_id )
2021-09-06 19:21:18 -07:00
return next_diagnostic ( cursor_position , true , bufnr , opts , opts.namespace )
end
--- Return the position of the next diagnostic in the current buffer.
---
2024-01-09 05:47:57 -07:00
---@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.
2021-09-06 19:21:18 -07:00
function M . get_next_pos ( opts )
2021-09-16 10:26:45 -07:00
local next = M.get_next ( opts )
if not next then
return false
end
return { next.lnum , next.col }
2021-09-06 19:21:18 -07:00
end
2024-01-09 05:47:57 -07:00
--- @class vim.diagnostic.GetOpts
--- @field namespace? integer
--- @field lnum? integer
2024-01-22 23:04:20 -07:00
--- @field severity? vim.diagnostic.SeverityFilter
2024-01-09 05:47:57 -07:00
--- @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
2021-09-06 19:21:18 -07:00
--- Move to the next diagnostic.
---
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.GotoOpts (table) Configuration table with the following keys:
--- - namespace: (integer) Only consider diagnostics from the given namespace.
2023-08-09 02:06:13 -07:00
--- - cursor_position: (cursor position) Cursor position as a (row, col) tuple.
--- See |nvim_win_get_cursor()|. Defaults to the current cursor position.
2021-09-06 19:21:18 -07:00
--- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'.
--- - severity: See |diagnostic-severity|.
2021-10-19 10:45:51 -07:00
--- - float: (boolean or table, default true) If "true", call |vim.diagnostic.open_float()|
2023-08-09 02:06:13 -07:00
--- after moving. If a table, pass the table as the {opts} parameter
--- to |vim.diagnostic.open_float()|. Unless overridden, the float will show
2021-09-06 19:21:18 -07:00
--- diagnostics at the new cursor position (as if "cursor" were passed to
2021-10-19 10:45:51 -07:00
--- the "scope" option).
2021-09-06 19:21:18 -07:00
--- - win_id: (number, default 0) Window ID
function M . goto_next ( opts )
2024-01-09 05:47:57 -07:00
diagnostic_move_pos ( opts , M.get_next_pos ( opts ) )
2021-09-06 19:21:18 -07:00
end
2021-10-29 18:47:34 -07:00
M.handlers . signs = {
show = function ( namespace , bufnr , diagnostics , opts )
vim.validate ( {
namespace = { namespace , ' n ' } ,
bufnr = { bufnr , ' n ' } ,
2022-01-01 12:58:34 -07:00
diagnostics = {
diagnostics ,
vim.tbl_islist ,
' a list of diagnostics ' ,
} ,
2021-10-29 18:47:34 -07:00
opts = { opts , ' t ' , true } ,
} )
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
bufnr = get_bufnr ( bufnr )
2022-01-11 16:39:15 -07:00
opts = opts or { }
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
if opts.signs and opts.signs . severity then
diagnostics = filter_by_severity ( opts.signs . severity , diagnostics )
end
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
-- 10 is the default sign priority when none is explicitly specified
local priority = opts.signs and opts.signs . priority or 10
2024-01-09 05:47:57 -07:00
local get_priority --- @type function
2021-10-29 18:47:34 -07:00
if opts.severity_sort then
if type ( opts.severity_sort ) == ' table ' and opts.severity_sort . reverse then
get_priority = function ( severity )
return priority + ( severity - vim.diagnostic . severity.ERROR )
end
else
get_priority = function ( severity )
return priority + ( vim.diagnostic . severity.HINT - severity )
end
2021-09-26 16:02:18 -07:00
end
else
2021-10-29 18:47:34 -07:00
get_priority = function ( )
return priority
2021-09-26 16:02:18 -07:00
end
end
2021-10-29 18:47:34 -07:00
local ns = M.get_namespace ( namespace )
2023-12-13 07:19:53 -07:00
if not ns.user_data . sign_ns then
2023-12-14 08:19:33 -07:00
ns.user_data . sign_ns =
api.nvim_create_namespace ( string.format ( ' %s/diagnostic/signs ' , ns.name ) )
2023-12-13 07:19:53 -07:00
end
2023-12-18 10:04:44 -07:00
--- Handle legacy diagnostic sign definitions
--- These were deprecated in 0.10 and will be removed in 0.12
if opts.signs and not opts.signs . text and not opts.signs . numhl and not opts.signs . texthl then
for _ , v in ipairs ( { ' Error ' , ' Warn ' , ' Info ' , ' Hint ' } ) do
local name = string.format ( ' DiagnosticSign%s ' , v )
local sign = vim.fn . sign_getdefined ( name ) [ 1 ]
if sign then
local severity = M.severity [ v : upper ( ) ]
2023-12-25 13:28:28 -07:00
vim.deprecate (
' Defining diagnostic signs with :sign-define or sign_define() ' ,
' vim.diagnostic.config() ' ,
' 0.12 ' ,
nil ,
false
)
2023-12-18 10:04:44 -07:00
if not opts.signs . text then
opts.signs . text = { }
end
if not opts.signs . numhl then
opts.signs . numhl = { }
end
if not opts.signs . linehl then
opts.signs . linehl = { }
end
if opts.signs . text [ severity ] == nil then
opts.signs . text [ severity ] = sign.text or ' '
end
if opts.signs . numhl [ severity ] == nil then
opts.signs . numhl [ severity ] = sign.numhl
end
if opts.signs . linehl [ severity ] == nil then
opts.signs . linehl [ severity ] = sign.linehl
end
end
end
end
2024-01-09 05:47:57 -07:00
local text = { } ---@type table<vim.diagnostic.Severity|string, string>
2023-12-13 07:19:53 -07:00
for k in pairs ( M.severity ) do
if opts.signs . text and opts.signs . text [ k ] then
text [ k ] = opts.signs . text [ k ]
elseif type ( k ) == ' string ' and not text [ k ] then
text [ k ] = string.sub ( k , 1 , 1 ) : upper ( )
end
2021-10-29 18:47:34 -07:00
end
2021-09-06 19:21:18 -07:00
2023-12-13 08:54:04 -07:00
local numhl = opts.signs . numhl or { }
local linehl = opts.signs . linehl or { }
2021-10-29 18:47:34 -07:00
for _ , diagnostic in ipairs ( diagnostics ) do
2023-12-13 07:19:53 -07:00
if api.nvim_buf_is_loaded ( diagnostic.bufnr ) then
api.nvim_buf_set_extmark ( bufnr , ns.user_data . sign_ns , diagnostic.lnum , 0 , {
sign_text = text [ diagnostic.severity ] or text [ M.severity [ diagnostic.severity ] ] or ' U ' ,
sign_hl_group = sign_highlight_map [ diagnostic.severity ] ,
2023-12-13 08:54:04 -07:00
number_hl_group = numhl [ diagnostic.severity ] ,
line_hl_group = linehl [ diagnostic.severity ] ,
2023-12-13 07:19:53 -07:00
priority = get_priority ( diagnostic.severity ) ,
} )
end
2021-10-29 18:47:34 -07:00
end
end ,
2024-01-09 05:47:57 -07:00
--- @param namespace integer
--- @param bufnr integer
2021-10-29 18:47:34 -07:00
hide = function ( namespace , bufnr )
local ns = M.get_namespace ( namespace )
2023-12-13 08:26:39 -07:00
if ns.user_data . sign_ns and api.nvim_buf_is_valid ( bufnr ) then
2023-12-13 07:19:53 -07:00
api.nvim_buf_clear_namespace ( bufnr , ns.user_data . sign_ns , 0 , - 1 )
2021-10-29 18:47:34 -07:00
end
end ,
}
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
M.handlers . underline = {
show = function ( namespace , bufnr , diagnostics , opts )
vim.validate ( {
namespace = { namespace , ' n ' } ,
bufnr = { bufnr , ' n ' } ,
2022-01-01 12:58:34 -07:00
diagnostics = {
diagnostics ,
vim.tbl_islist ,
' a list of diagnostics ' ,
} ,
2021-10-29 18:47:34 -07:00
opts = { opts , ' t ' , true } ,
} )
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
bufnr = get_bufnr ( bufnr )
2022-01-11 16:39:15 -07:00
opts = opts or { }
2021-09-06 19:21:18 -07:00
2023-10-16 18:51:36 -07:00
if not vim.api . nvim_buf_is_loaded ( bufnr ) then
return
end
2021-10-29 18:47:34 -07:00
if opts.underline and opts.underline . severity then
diagnostics = filter_by_severity ( opts.underline . severity , diagnostics )
2021-09-06 19:21:18 -07:00
end
2021-10-29 18:47:34 -07:00
local ns = M.get_namespace ( namespace )
if not ns.user_data . underline_ns then
2023-12-14 08:19:33 -07:00
ns.user_data . underline_ns =
api.nvim_create_namespace ( string.format ( ' %s/diagnostic/underline ' , ns.name ) )
2021-10-29 18:47:34 -07:00
end
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
local underline_ns = ns.user_data . underline_ns
for _ , diagnostic in ipairs ( diagnostics ) do
2024-01-09 05:47:57 -07:00
--- @type string?
local higroup = underline_highlight_map [ assert ( diagnostic.severity ) ]
2021-09-06 19:21:18 -07:00
2021-10-29 18:47:34 -07:00
if higroup == nil then
-- Default to error if we don't have a highlight associated
2024-01-09 05:47:57 -07:00
-- TODO(lewis6991): this is always nil since underline_highlight_map only has integer keys
2021-10-29 18:47:34 -07:00
higroup = underline_highlight_map.Error
end
2021-09-06 19:21:18 -07:00
2023-03-30 06:49:58 -07:00
if diagnostic._tags then
-- TODO(lewis6991): we should be able to stack these.
if diagnostic._tags . unnecessary then
higroup = ' DiagnosticUnnecessary '
end
if diagnostic._tags . deprecated then
higroup = ' DiagnosticDeprecated '
end
end
2021-10-29 18:47:34 -07:00
vim.highlight . range (
bufnr ,
underline_ns ,
higroup ,
{ diagnostic.lnum , diagnostic.col } ,
2022-02-19 09:38:14 -07:00
{ diagnostic.end_lnum , diagnostic.end_col } ,
2022-02-21 13:21:42 -07:00
{ priority = vim.highlight . priorities.diagnostics }
2021-10-29 18:47:34 -07:00
)
end
2021-11-09 14:33:01 -07:00
save_extmarks ( underline_ns , bufnr )
2021-10-29 18:47:34 -07:00
end ,
hide = function ( namespace , bufnr )
local ns = M.get_namespace ( namespace )
if ns.user_data . underline_ns then
2021-11-09 14:33:01 -07:00
diagnostic_cache_extmarks [ bufnr ] [ ns.user_data . underline_ns ] = { }
2022-12-17 19:19:15 -07:00
if api.nvim_buf_is_valid ( bufnr ) then
api.nvim_buf_clear_namespace ( bufnr , ns.user_data . underline_ns , 0 , - 1 )
end
2021-10-29 18:47:34 -07:00
end
2021-09-22 12:20:15 -07:00
end ,
2021-10-29 18:47:34 -07:00
}
2021-09-22 12:20:15 -07:00
2021-10-29 18:47:34 -07:00
M.handlers . virtual_text = {
show = function ( namespace , bufnr , diagnostics , opts )
vim.validate ( {
namespace = { namespace , ' n ' } ,
bufnr = { bufnr , ' n ' } ,
2022-01-01 12:58:34 -07:00
diagnostics = {
diagnostics ,
vim.tbl_islist ,
' a list of diagnostics ' ,
} ,
2021-10-29 18:47:34 -07:00
opts = { opts , ' t ' , true } ,
} )
bufnr = get_bufnr ( bufnr )
2022-01-11 16:39:15 -07:00
opts = opts or { }
2021-09-18 14:00:32 -07:00
2023-10-16 18:51:36 -07:00
if not vim.api . nvim_buf_is_loaded ( bufnr ) then
return
end
2024-01-22 23:04:20 -07:00
local severity --- @type vim.diagnostic.SeverityFilter?
2021-10-29 18:47:34 -07:00
if opts.virtual_text then
if opts.virtual_text . format then
diagnostics = reformat_diagnostics ( opts.virtual_text . format , diagnostics )
end
2021-12-17 19:38:33 -07:00
if
opts.virtual_text . source
and ( opts.virtual_text . source ~= ' if_many ' or count_sources ( bufnr ) > 1 )
then
diagnostics = prefix_source ( diagnostics )
2021-10-29 18:47:34 -07:00
end
if opts.virtual_text . severity then
severity = opts.virtual_text . severity
end
2021-09-06 19:21:18 -07:00
end
2021-10-29 18:47:34 -07:00
local ns = M.get_namespace ( namespace )
if not ns.user_data . virt_text_ns then
2023-12-14 08:19:33 -07:00
ns.user_data . virt_text_ns =
api.nvim_create_namespace ( string.format ( ' %s/diagnostic/virtual_text ' , ns.name ) )
2021-09-06 19:21:18 -07:00
end
2021-10-29 18:47:34 -07:00
local virt_text_ns = ns.user_data . virt_text_ns
local buffer_line_diagnostics = diagnostic_lines ( diagnostics )
for line , line_diagnostics in pairs ( buffer_line_diagnostics ) do
if severity then
line_diagnostics = filter_by_severity ( severity , line_diagnostics )
end
local virt_texts = M._get_virt_text_chunks ( line_diagnostics , opts.virtual_text )
if virt_texts then
2022-11-19 06:41:47 -07:00
api.nvim_buf_set_extmark ( bufnr , virt_text_ns , line , 0 , {
2023-08-16 06:21:32 -07:00
hl_mode = opts.virtual_text . hl_mode or ' combine ' ,
2021-10-29 18:47:34 -07:00
virt_text = virt_texts ,
2023-08-16 06:21:32 -07:00
virt_text_pos = opts.virtual_text . virt_text_pos ,
virt_text_hide = opts.virtual_text . virt_text_hide ,
virt_text_win_col = opts.virtual_text . virt_text_win_col ,
2021-10-29 18:47:34 -07:00
} )
end
end
2021-11-09 14:33:01 -07:00
save_extmarks ( virt_text_ns , bufnr )
2021-10-29 18:47:34 -07:00
end ,
hide = function ( namespace , bufnr )
local ns = M.get_namespace ( namespace )
if ns.user_data . virt_text_ns then
2021-11-09 14:33:01 -07:00
diagnostic_cache_extmarks [ bufnr ] [ ns.user_data . virt_text_ns ] = { }
2022-12-17 19:19:15 -07:00
if api.nvim_buf_is_valid ( bufnr ) then
api.nvim_buf_clear_namespace ( bufnr , ns.user_data . virt_text_ns , 0 , - 1 )
end
2021-10-29 18:47:34 -07:00
end
end ,
}
2021-09-06 19:21:18 -07:00
--- Get virtual text chunks to display using |nvim_buf_set_extmark()|.
---
2021-09-20 14:32:14 -07:00
--- 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.
2024-01-09 05:47:57 -07:00
--- @private
--- @param line_diags table<integer,vim.Diagnostic>
--- @param opts vim.diagnostic.Opts.VirtualText
2021-09-20 14:32:14 -07:00
function M . _get_virt_text_chunks ( line_diags , opts )
2021-09-06 19:21:18 -07:00
if # line_diags == 0 then
return nil
end
opts = opts or { }
local prefix = opts.prefix or ' ■ '
2022-11-20 16:57:36 -07:00
local suffix = opts.suffix or ' '
2021-09-06 19:21:18 -07:00
local spacing = opts.spacing or 4
-- Create a little more space between virtual text and contents
local virt_texts = { { string.rep ( ' ' , spacing ) } }
2023-04-17 04:53:34 -07:00
for i = 1 , # line_diags do
local resolved_prefix = prefix
if type ( prefix ) == ' function ' then
2023-10-27 06:17:46 -07:00
resolved_prefix = prefix ( line_diags [ i ] , i , # line_diags ) or ' '
2023-04-17 04:53:34 -07:00
end
table.insert (
virt_texts ,
{ resolved_prefix , virtual_text_highlight_map [ line_diags [ i ] . severity ] }
)
2021-09-06 19:21:18 -07:00
end
local last = line_diags [ # line_diags ]
-- TODO(tjdevries): Allow different servers to be shown first somehow?
-- TODO(tjdevries): Display server name associated with these?
if last.message then
2022-11-20 16:57:36 -07:00
if type ( suffix ) == ' function ' then
suffix = suffix ( last ) or ' '
end
2021-09-06 19:21:18 -07:00
table.insert ( virt_texts , {
2023-04-17 04:53:34 -07:00
string.format ( ' %s%s ' , last.message : gsub ( ' \r ' , ' ' ) : gsub ( ' \n ' , ' ' ) , suffix ) ,
2021-09-06 19:21:18 -07:00
virtual_text_highlight_map [ last.severity ] ,
} )
return virt_texts
end
end
--- Hide currently displayed diagnostics.
---
--- This only clears the decorations displayed in the buffer. Diagnostics can
--- be redisplayed with |vim.diagnostic.show()|. To completely remove
--- diagnostics, use |vim.diagnostic.reset()|.
---
--- To hide diagnostics and prevent them from re-displaying, use
--- |vim.diagnostic.disable()|.
---
2024-01-09 05:47:57 -07:00
---@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.
2021-09-06 19:21:18 -07:00
function M . hide ( namespace , bufnr )
vim.validate ( {
2021-11-09 14:33:01 -07:00
namespace = { namespace , ' n ' , true } ,
2021-09-06 19:21:18 -07:00
bufnr = { bufnr , ' n ' , true } ,
} )
2021-11-16 08:47:49 -07:00
local buffers = bufnr and { get_bufnr ( bufnr ) } or vim.tbl_keys ( diagnostic_cache )
for _ , iter_bufnr in ipairs ( buffers ) do
local namespaces = namespace and { namespace } or vim.tbl_keys ( diagnostic_cache [ iter_bufnr ] )
for _ , iter_namespace in ipairs ( namespaces ) do
for _ , handler in pairs ( M.handlers ) do
if handler.hide then
handler.hide ( iter_namespace , iter_bufnr )
end
2021-11-09 14:33:01 -07:00
end
2021-10-29 18:47:34 -07:00
end
end
2021-09-06 19:21:18 -07:00
end
2023-01-12 09:57:39 -07:00
--- Check whether diagnostics are disabled in a given buffer.
---
2024-01-09 05:47:57 -07:00
---@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.
2023-01-12 09:57:39 -07:00
---@return boolean
function M . is_disabled ( bufnr , namespace )
bufnr = get_bufnr ( bufnr )
if namespace and M.get_namespace ( namespace ) . disabled then
return true
end
if type ( diagnostic_disabled [ bufnr ] ) == ' table ' then
return diagnostic_disabled [ bufnr ] [ namespace ]
end
return diagnostic_disabled [ bufnr ] ~= nil
end
2021-09-06 19:21:18 -07:00
--- Display diagnostics for the given namespace and buffer.
---
2024-01-09 05:47:57 -07:00
---@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
2021-09-06 19:21:18 -07:00
--- 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
2021-11-16 08:47:49 -07:00
--- diagnostics. May not be used when {namespace}
--- or {bufnr} is nil.
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.Opts (table) Display options. See |vim.diagnostic.config()|.
2021-09-06 19:21:18 -07:00
function M . show ( namespace , bufnr , diagnostics , opts )
vim.validate ( {
2021-11-09 14:33:01 -07:00
namespace = { namespace , ' n ' , true } ,
2021-09-06 19:21:18 -07:00
bufnr = { bufnr , ' n ' , true } ,
2022-01-01 12:58:34 -07:00
diagnostics = {
diagnostics ,
function ( v )
return v == nil or vim.tbl_islist ( v )
end ,
' a list of diagnostics ' ,
} ,
2021-09-06 19:21:18 -07:00
opts = { opts , ' t ' , true } ,
} )
2021-11-16 08:47:49 -07:00
if not bufnr or not namespace then
assert ( not diagnostics , ' Cannot show diagnostics without a buffer and namespace ' )
if not bufnr then
for iter_bufnr in pairs ( diagnostic_cache ) do
M.show ( namespace , iter_bufnr , nil , opts )
end
else
-- namespace is nil
bufnr = get_bufnr ( bufnr )
for iter_namespace in pairs ( diagnostic_cache [ bufnr ] ) do
M.show ( iter_namespace , bufnr , nil , opts )
end
2021-11-09 14:33:01 -07:00
end
return
end
2023-01-12 09:57:39 -07:00
if M.is_disabled ( bufnr , namespace ) then
2021-09-06 19:21:18 -07:00
return
end
M.hide ( namespace , bufnr )
2021-11-19 11:20:04 -07:00
diagnostics = diagnostics or get_diagnostics ( bufnr , { namespace = namespace } , true )
2021-09-06 19:21:18 -07:00
2024-01-09 05:47:57 -07:00
if vim.tbl_isempty ( diagnostics ) then
2021-09-06 19:21:18 -07:00
return
end
2024-01-09 05:47:57 -07:00
local opts_res = get_resolved_options ( opts , namespace , bufnr )
2021-09-06 19:21:18 -07:00
2024-01-09 05:47:57 -07:00
if opts_res.update_in_insert then
2021-09-06 19:21:18 -07:00
clear_scheduled_display ( namespace , bufnr )
else
2022-11-19 06:41:47 -07:00
local mode = api.nvim_get_mode ( )
2021-09-06 19:21:18 -07:00
if string.sub ( mode.mode , 1 , 1 ) == ' i ' then
2024-01-09 05:47:57 -07:00
schedule_display ( namespace , bufnr , opts_res )
2021-09-06 19:21:18 -07:00
return
end
end
2024-01-09 05:47:57 -07:00
if if_nil ( opts_res.severity_sort , false ) then
if type ( opts_res.severity_sort ) == ' table ' and opts_res.severity_sort . reverse then
2021-09-17 09:50:25 -07:00
table.sort ( diagnostics , function ( a , b )
return a.severity < b.severity
end )
2021-09-17 13:59:30 -07:00
else
table.sort ( diagnostics , function ( a , b )
return a.severity > b.severity
end )
2021-09-17 09:50:25 -07:00
end
end
2021-10-29 18:47:34 -07:00
for handler_name , handler in pairs ( M.handlers ) do
2024-01-09 05:47:57 -07:00
if handler.show and opts_res [ handler_name ] then
handler.show ( namespace , bufnr , diagnostics , opts_res )
2021-10-29 18:47:34 -07:00
end
2021-09-06 19:21:18 -07:00
end
end
2021-10-19 10:45:51 -07:00
--- Show diagnostics in a floating window.
2021-09-06 19:21:18 -07:00
---
2024-01-09 05:47:57 -07:00
---@param opts vim.diagnostic.Opts.Float? (table?) Configuration table with the same keys
2023-08-09 02:06:13 -07:00
--- as |vim.lsp.util.open_floating_preview()| in addition to the following:
2021-12-08 18:44:31 -07:00
--- - bufnr: (number) Buffer number to show diagnostics from.
--- Defaults to the current buffer.
2021-09-06 19:21:18 -07:00
--- - namespace: (number) Limit diagnostics to the given namespace
2021-11-28 09:42:29 -07:00
--- - scope: (string, default "line") Show diagnostics from the whole buffer ("buffer"),
2021-10-19 10:45:51 -07:00
--- the current cursor line ("line"), or the current cursor position ("cursor").
2021-12-08 18:44:31 -07:00
--- Shorthand versions are also accepted ("c" for "cursor", "l" for "line", "b"
--- for "buffer").
2021-10-19 10:45:51 -07:00
--- - 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()|.
2023-08-09 02:06:13 -07:00
--- - severity: See |diagnostic-severity|. Overrides the setting
--- from |vim.diagnostic.config()|.
2021-11-15 08:56:10 -07:00
--- - header: (string or table) String to use as the header for the floating window. If a
--- table, it is interpreted as a [text, hl_group] tuple. Overrides the setting
--- from |vim.diagnostic.config()|.
2021-12-17 19:38:33 -07:00
--- - source: (boolean or string) Include the diagnostic source in the message.
--- Use "if_many" to only show sources if there is more than one source of
--- diagnostics in the buffer. Otherwise, any truthy value means to always show
2023-08-09 02:06:13 -07:00
--- the diagnostic source. Overrides the setting from |vim.diagnostic.config()|.
2021-09-22 12:20:15 -07:00
--- - 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.
2021-10-19 10:45:51 -07:00
--- Overrides the setting from |vim.diagnostic.config()|.
2021-11-15 08:55:31 -07:00
--- - prefix: (function, string, or table) Prefix each diagnostic in the floating
--- window. If a function, it must have the signature (diagnostic, i,
--- total) -> (string, string), where {i} is the index of the diagnostic
--- being evaluated and {total} is the total number of diagnostics
--- displayed in the window. The function should return a string which
--- is prepended to each diagnostic in the window as well as an
--- (optional) highlight group which will be used to highlight the
--- prefix. If {prefix} is a table, it is interpreted as a [text,
--- hl_group] tuple as in |nvim_echo()|; otherwise, if {prefix} is a
--- string, it is prepended to each diagnostic in the window with no
--- highlight.
2021-11-14 18:40:11 -07:00
--- Overrides the setting from |vim.diagnostic.config()|.
2022-11-20 13:09:35 -07:00
--- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of
--- prepending it. Overrides the setting from |vim.diagnostic.config()|.
2024-01-09 05:47:57 -07:00
---@return integer?, integer?: ({float_bufnr}, {win_id})
2021-12-08 18:44:31 -07:00
function M . open_float ( opts , ... )
-- Support old (bufnr, opts) signature
2024-01-09 05:47:57 -07:00
local bufnr --- @type integer?
2021-12-08 18:44:31 -07:00
if opts == nil or type ( opts ) == ' number ' then
bufnr = opts
2024-01-09 05:47:57 -07:00
opts = ... --- @type vim.diagnostic.Opts.Float
2021-12-08 18:44:31 -07:00
else
vim.validate ( {
opts = { opts , ' t ' , true } ,
} )
end
2021-09-06 19:21:18 -07:00
opts = opts or { }
2021-12-08 18:44:31 -07:00
bufnr = get_bufnr ( bufnr or opts.bufnr )
2022-03-27 07:10:03 -07:00
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
2021-12-08 18:44:31 -07:00
local scope = ( { l = ' line ' , c = ' cursor ' , b = ' buffer ' } ) [ opts.scope ] or opts.scope or ' line '
2024-01-09 05:47:57 -07:00
local lnum , col --- @type integer, integer
local opts_pos = opts.pos
2021-10-19 10:45:51 -07:00
if scope == ' line ' or scope == ' cursor ' then
2024-01-09 05:47:57 -07:00
if not opts_pos then
2022-11-19 06:41:47 -07:00
local pos = api.nvim_win_get_cursor ( 0 )
2021-10-19 10:45:51 -07:00
lnum = pos [ 1 ] - 1
col = pos [ 2 ]
2024-01-09 05:47:57 -07:00
elseif type ( opts_pos ) == ' number ' then
lnum = opts_pos
elseif type ( opts_pos ) == ' table ' then
lnum , col = opts_pos [ 1 ] , opts_pos [ 2 ]
2021-10-19 10:45:51 -07:00
else
error ( " Invalid value for option 'pos' " )
end
elseif scope ~= ' buffer ' then
error ( " Invalid value for option 'scope' " )
2021-09-06 19:21:18 -07:00
end
2021-10-19 10:45:51 -07:00
2024-01-09 05:47:57 -07:00
local diagnostics = get_diagnostics ( bufnr , opts --[[@as vim.diagnostic.GetOpts]] , true )
2021-09-06 19:21:18 -07:00
2021-10-19 10:45:51 -07:00
if scope == ' line ' then
2024-01-09 05:47:57 -07:00
--- @param d vim.Diagnostic
2021-10-19 10:45:51 -07:00
diagnostics = vim.tbl_filter ( function ( d )
return d.lnum == lnum
end , diagnostics )
elseif scope == ' cursor ' then
2021-10-19 15:27:49 -07:00
-- LSP servers can send diagnostics with `end_col` past the length of the line
2022-11-19 06:41:47 -07:00
local line_length = # api.nvim_buf_get_lines ( bufnr , lnum , lnum + 1 , true ) [ 1 ]
2024-01-09 05:47:57 -07:00
--- @param d vim.Diagnostic
2021-10-19 10:45:51 -07:00
diagnostics = vim.tbl_filter ( function ( d )
2021-10-19 15:27:49 -07:00
return d.lnum == lnum
and math.min ( d.col , line_length - 1 ) <= col
and ( d.end_col >= col or d.end_lnum > lnum )
2021-10-19 10:45:51 -07:00
end , diagnostics )
end
2021-09-06 19:21:18 -07:00
2021-10-19 10:45:51 -07:00
if vim.tbl_isempty ( diagnostics ) then
return
end
2024-01-09 05:47:57 -07:00
local severity_sort = if_nil ( opts.severity_sort , global_diagnostic_options.severity_sort )
2021-10-19 10:45:51 -07:00
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
2024-01-09 05:47:57 -07:00
local lines = { } --- @type string[]
local highlights = { } --- @type table[]
2021-11-15 08:56:10 -07:00
local header = if_nil ( opts.header , ' Diagnostics: ' )
if header then
vim.validate ( {
header = {
header ,
2022-11-19 06:41:47 -07:00
{ ' string ' , ' table ' } ,
2021-11-15 08:56:10 -07:00
" 'string' or 'table' " ,
} ,
} )
if type ( header ) == ' table ' then
-- Don't insert any lines for an empty string
if string.len ( if_nil ( header [ 1 ] , ' ' ) ) > 0 then
table.insert ( lines , header [ 1 ] )
2022-11-20 13:09:35 -07:00
table.insert ( highlights , { hlname = header [ 2 ] or ' Bold ' } )
2021-11-15 08:56:10 -07:00
end
elseif # header > 0 then
table.insert ( lines , header )
2022-11-20 13:09:35 -07:00
table.insert ( highlights , { hlname = ' Bold ' } )
2021-11-15 08:56:10 -07:00
end
2021-10-19 10:45:51 -07:00
end
if opts.format then
diagnostics = reformat_diagnostics ( opts.format , diagnostics )
end
2021-12-17 19:38:33 -07:00
if opts.source and ( opts.source ~= ' if_many ' or count_sources ( bufnr ) > 1 ) then
diagnostics = prefix_source ( diagnostics )
2021-10-19 10:45:51 -07:00
end
2021-11-14 18:40:11 -07:00
local prefix_opt =
if_nil ( opts.prefix , ( scope == ' cursor ' and # diagnostics <= 1 ) and ' ' or function ( _ , i )
return string.format ( ' %d. ' , i )
end )
2021-11-15 08:55:31 -07:00
2024-01-09 05:47:57 -07:00
local prefix , prefix_hl_group --- @type string?, string?
2021-11-14 18:40:11 -07:00
if prefix_opt then
vim.validate ( {
prefix = {
prefix_opt ,
2022-11-19 06:41:47 -07:00
{ ' string ' , ' table ' , ' function ' } ,
2021-11-15 08:55:31 -07:00
" 'string' or 'table' or 'function' " ,
} ,
} )
if type ( prefix_opt ) == ' string ' then
prefix , prefix_hl_group = prefix_opt , ' NormalFloat '
elseif type ( prefix_opt ) == ' table ' then
prefix , prefix_hl_group = prefix_opt [ 1 ] or ' ' , prefix_opt [ 2 ] or ' NormalFloat '
end
2021-11-14 18:40:11 -07:00
end
2022-11-20 13:09:35 -07:00
local suffix_opt = if_nil ( opts.suffix , function ( diagnostic )
return diagnostic.code and string.format ( ' [%s] ' , diagnostic.code ) or ' '
end )
2024-01-09 05:47:57 -07:00
local suffix , suffix_hl_group --- @type string?, string?
2022-11-20 13:09:35 -07:00
if suffix_opt then
vim.validate ( {
suffix = {
suffix_opt ,
{ ' string ' , ' table ' , ' function ' } ,
" 'string' or 'table' or 'function' " ,
} ,
} )
if type ( suffix_opt ) == ' string ' then
suffix , suffix_hl_group = suffix_opt , ' NormalFloat '
elseif type ( suffix_opt ) == ' table ' then
suffix , suffix_hl_group = suffix_opt [ 1 ] or ' ' , suffix_opt [ 2 ] or ' NormalFloat '
end
end
2021-10-19 10:45:51 -07:00
for i , diagnostic in ipairs ( diagnostics ) do
2024-01-09 05:47:57 -07:00
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 '
2021-11-15 08:55:31 -07:00
end
2024-01-09 05:47:57 -07:00
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 '
2022-11-20 13:09:35 -07:00
end
2024-01-09 05:47:57 -07:00
--- @type string?
local hiname = floating_highlight_map [ assert ( diagnostic.severity ) ]
2021-10-19 10:45:51 -07:00
local message_lines = vim.split ( diagnostic.message , ' \n ' )
2022-11-20 13:09:35 -07:00
for j = 1 , # message_lines do
local pre = j == 1 and prefix or string.rep ( ' ' , # prefix )
local suf = j == # message_lines and suffix or ' '
table.insert ( lines , pre .. message_lines [ j ] .. suf )
table.insert ( highlights , {
hlname = hiname ,
prefix = {
length = j == 1 and # prefix or 0 ,
hlname = prefix_hl_group ,
} ,
suffix = {
length = j == # message_lines and # suffix or 0 ,
hlname = suffix_hl_group ,
} ,
} )
2021-10-19 10:45:51 -07:00
end
end
2021-10-19 15:29:52 -07:00
-- Used by open_floating_preview to allow the float to be focused
if not opts.focus_id then
opts.focus_id = scope
end
2024-01-22 10:23:28 -07:00
local float_bufnr , winnr = vim.lsp . util.open_floating_preview ( lines , ' plaintext ' , opts )
2022-11-20 13:09:35 -07:00
for i , hl in ipairs ( highlights ) do
local line = lines [ i ]
local prefix_len = hl.prefix and hl.prefix . length or 0
local suffix_len = hl.suffix and hl.suffix . length or 0
if prefix_len > 0 then
api.nvim_buf_add_highlight ( float_bufnr , - 1 , hl.prefix . hlname , i - 1 , 0 , prefix_len )
end
api.nvim_buf_add_highlight ( float_bufnr , - 1 , hl.hlname , i - 1 , prefix_len , # line - suffix_len )
if suffix_len > 0 then
api.nvim_buf_add_highlight ( float_bufnr , - 1 , hl.suffix . hlname , i - 1 , # line - suffix_len , - 1 )
2021-11-15 08:55:31 -07:00
end
2021-10-19 10:45:51 -07:00
end
return float_bufnr , winnr
2021-09-06 19:21:18 -07:00
end
--- Remove all diagnostics from the given namespace.
---
--- Unlike |vim.diagnostic.hide()|, this function removes all saved
--- diagnostics. They cannot be redisplayed using |vim.diagnostic.show()|. To
--- simply remove diagnostic decorations in a way that they can be
--- re-displayed, use |vim.diagnostic.hide()|.
---
2024-01-09 05:47:57 -07:00
---@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.
2021-09-06 19:21:18 -07:00
function M . reset ( namespace , bufnr )
2021-11-09 14:33:01 -07:00
vim.validate ( {
namespace = { namespace , ' n ' , true } ,
bufnr = { bufnr , ' n ' , true } ,
} )
2021-11-22 08:47:30 -07:00
local buffers = bufnr and { get_bufnr ( bufnr ) } or vim.tbl_keys ( diagnostic_cache )
2021-11-09 14:33:01 -07:00
for _ , iter_bufnr in ipairs ( buffers ) do
local namespaces = namespace and { namespace } or vim.tbl_keys ( diagnostic_cache [ iter_bufnr ] )
for _ , iter_namespace in ipairs ( namespaces ) do
2021-11-21 18:40:06 -07:00
diagnostic_cache [ iter_bufnr ] [ iter_namespace ] = nil
2021-11-09 14:33:01 -07:00
M.hide ( iter_namespace , iter_bufnr )
2021-09-06 19:21:18 -07:00
end
2022-12-17 19:19:15 -07:00
if api.nvim_buf_is_valid ( iter_bufnr ) then
api.nvim_exec_autocmds ( ' DiagnosticChanged ' , {
modeline = false ,
buffer = iter_bufnr ,
data = { diagnostics = { } } ,
} )
else
diagnostic_cache [ iter_bufnr ] = nil
end
2022-01-03 06:48:01 -07:00
end
2021-09-06 19:21:18 -07:00
end
2024-01-09 05:47:57 -07:00
--- @class vim.diagnostic.setqflist.Opts
--- @field namespace? integer
--- @field open? boolean
--- @field title? string
--- @field severity? vim.diagnostic.Severity
2021-09-06 19:21:18 -07:00
--- Add all diagnostics to the quickfix list.
---
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.setqflist.Opts (table) Configuration table with the following keys:
2021-09-06 19:21:18 -07:00
--- - 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".
--- - severity: See |diagnostic-severity|.
function M . setqflist ( opts )
set_list ( false , opts )
end
2024-01-09 05:47:57 -07:00
--- @class vim.diagnostic.setloclist.Opts
--- @field namespace? integer
--- @field open? boolean
--- @field title? string
--- @field severity? vim.diagnostic.Severity
--- @field winnr? integer
2021-09-06 19:21:18 -07:00
--- Add buffer diagnostics to the location list.
---
2024-01-09 05:47:57 -07:00
---@param opts? vim.diagnostic.setloclist.Opts (table) Configuration table with the following keys:
2021-09-06 19:21:18 -07:00
--- - 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.
--- - title: (string) Title of the location list. Defaults to "Diagnostics".
--- - severity: See |diagnostic-severity|.
function M . setloclist ( opts )
set_list ( true , opts )
end
--- Disable diagnostics in the given buffer.
---
2024-01-09 05:47:57 -07:00
---@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.
2021-09-06 19:21:18 -07:00
function M . disable ( bufnr , namespace )
vim.validate ( { bufnr = { bufnr , ' n ' , true } , namespace = { namespace , ' n ' , true } } )
2021-11-16 08:47:49 -07:00
if bufnr == nil then
if namespace == nil then
-- Disable everything (including as yet non-existing buffers and
-- namespaces) by setting diagnostic_disabled to an empty table and set
-- its metatable to always return true. This metatable is removed
-- in enable()
diagnostic_disabled = setmetatable ( { } , {
__index = function ( )
return true
end ,
} )
else
local ns = M.get_namespace ( namespace )
ns.disabled = true
2021-09-06 19:21:18 -07:00
end
else
2021-11-16 08:47:49 -07:00
bufnr = get_bufnr ( bufnr )
if namespace == nil then
diagnostic_disabled [ bufnr ] = true
else
if type ( diagnostic_disabled [ bufnr ] ) ~= ' table ' then
diagnostic_disabled [ bufnr ] = { }
end
diagnostic_disabled [ bufnr ] [ namespace ] = true
2021-09-06 19:21:18 -07:00
end
end
2021-11-16 08:47:49 -07:00
M.hide ( namespace , bufnr )
2021-09-06 19:21:18 -07:00
end
--- Enable diagnostics in the given buffer.
---
2024-01-09 05:47:57 -07:00
---@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.
2021-09-06 19:21:18 -07:00
function M . enable ( bufnr , namespace )
vim.validate ( { bufnr = { bufnr , ' n ' , true } , namespace = { namespace , ' n ' , true } } )
2021-11-16 08:47:49 -07:00
if bufnr == nil then
if namespace == nil then
-- Enable everything by setting diagnostic_disabled to an empty table
diagnostic_disabled = { }
else
local ns = M.get_namespace ( namespace )
ns.disabled = false
2021-09-06 19:21:18 -07:00
end
else
2021-11-16 08:47:49 -07:00
bufnr = get_bufnr ( bufnr )
if namespace == nil then
diagnostic_disabled [ bufnr ] = nil
else
if type ( diagnostic_disabled [ bufnr ] ) ~= ' table ' then
return
end
diagnostic_disabled [ bufnr ] [ namespace ] = nil
2021-09-06 19:21:18 -07:00
end
end
2021-11-16 08:47:49 -07:00
M.show ( namespace , bufnr )
2021-09-06 19:21:18 -07:00
end
2021-09-19 15:13:23 -07:00
--- Parse a diagnostic from a string.
---
--- For example, consider a line of output from a linter:
2023-09-14 06:23:01 -07:00
---
--- ```
2021-09-19 15:13:23 -07:00
--- WARNING filename:27:3: Variable 'foo' does not exist
2023-09-14 06:23:01 -07:00
--- ```
2021-10-17 07:18:35 -07:00
---
2021-09-19 15:13:23 -07:00
--- This can be parsed into a diagnostic |diagnostic-structure|
--- with:
2023-09-14 06:23:01 -07:00
---
--- ```lua
--- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
--- local groups = { "severity", "lnum", "col", "message" }
--- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
--- ```
2021-09-19 15:13:23 -07:00
---
---@param str string String to parse diagnostics from.
---@param pat string Lua pattern with capture groups.
2024-01-09 05:47:57 -07:00
---@param groups string[] List of fields in a |diagnostic-structure| to
2021-09-19 15:13:23 -07:00
--- associate with captures from {pat}.
---@param severity_map table A table mapping the severity field from {groups}
--- with an item from |vim.diagnostic.severity|.
2024-01-09 05:47:57 -07:00
---@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}.
2021-09-19 15:13:23 -07:00
function M . match ( str , pat , groups , severity_map , defaults )
vim.validate ( {
str = { str , ' s ' } ,
pat = { pat , ' s ' } ,
groups = { groups , ' t ' } ,
severity_map = { severity_map , ' t ' , true } ,
defaults = { defaults , ' t ' , true } ,
} )
2024-01-09 05:47:57 -07:00
--- @type table<string,vim.diagnostic.Severity>
2021-09-19 15:13:23 -07:00
severity_map = severity_map or M.severity
2024-01-09 05:47:57 -07:00
local matches = { str : match ( pat ) } --- @type any[]
2021-09-19 15:13:23 -07:00
if vim.tbl_isempty ( matches ) then
return
end
2024-01-09 05:47:57 -07:00
local diagnostic = { } --- @type type<string,any>
2021-09-19 15:13:23 -07:00
for i , match in ipairs ( matches ) do
local field = groups [ i ]
if field == ' severity ' then
match = severity_map [ match ]
elseif field == ' lnum ' or field == ' end_lnum ' or field == ' col ' or field == ' end_col ' then
match = assert ( tonumber ( match ) ) - 1
end
2024-01-09 05:47:57 -07:00
diagnostic [ field ] = match --- @type any
2021-09-19 15:13:23 -07:00
end
2021-09-06 19:21:18 -07:00
2024-01-09 05:47:57 -07:00
diagnostic = vim.tbl_extend ( ' keep ' , diagnostic , defaults or { } ) --- @type vim.Diagnostic
2021-09-19 15:13:23 -07:00
diagnostic.severity = diagnostic.severity or M.severity . ERROR
diagnostic.col = diagnostic.col or 0
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
diagnostic.end_col = diagnostic.end_col or diagnostic.col
return diagnostic
end
local errlist_type_map = {
[ M.severity . ERROR ] = ' E ' ,
[ M.severity . WARN ] = ' W ' ,
[ M.severity . INFO ] = ' I ' ,
[ M.severity . HINT ] = ' N ' ,
}
--- Convert a list of diagnostics to a list of quickfix items that can be
--- passed to |setqflist()| or |setloclist()|.
---
2024-01-09 05:47:57 -07:00
---@param diagnostics vim.Diagnostic[] List of diagnostics |diagnostic-structure|.
2022-11-21 14:02:18 -07:00
---@return table[] of quickfix list items |setqflist-what|
2021-09-19 15:13:23 -07:00
function M . toqflist ( diagnostics )
2022-01-01 12:58:34 -07:00
vim.validate ( {
diagnostics = {
diagnostics ,
vim.tbl_islist ,
' a list of diagnostics ' ,
} ,
} )
2021-09-19 15:13:23 -07:00
2024-01-09 05:47:57 -07:00
local list = { } --- @type table[]
2021-09-19 15:13:23 -07:00
for _ , v in ipairs ( diagnostics ) do
local item = {
bufnr = v.bufnr ,
lnum = v.lnum + 1 ,
col = v.col and ( v.col + 1 ) or nil ,
end_lnum = v.end_lnum and ( v.end_lnum + 1 ) or nil ,
end_col = v.end_col and ( v.end_col + 1 ) or nil ,
text = v.message ,
type = errlist_type_map [ v.severity ] or ' E ' ,
}
table.insert ( list , item )
end
table.sort ( list , function ( a , b )
if a.bufnr == b.bufnr then
2022-12-18 08:17:15 -07:00
if a.lnum == b.lnum then
return a.col < b.col
else
return a.lnum < b.lnum
end
2021-09-19 15:13:23 -07:00
else
return a.bufnr < b.bufnr
end
end )
return list
end
--- Convert a list of quickfix items to a list of diagnostics.
---
2023-08-09 02:06:13 -07:00
---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|.
2024-01-09 05:47:57 -07:00
---@return vim.Diagnostic[] array of |diagnostic-structure|
2021-09-19 15:13:23 -07:00
function M . fromqflist ( list )
2022-01-01 12:58:34 -07:00
vim.validate ( {
list = {
list ,
vim.tbl_islist ,
' a list of quickfix items ' ,
} ,
} )
2021-09-19 15:13:23 -07:00
2024-01-09 05:47:57 -07:00
local diagnostics = { } --- @type vim.Diagnostic[]
2021-09-19 15:13:23 -07:00
for _ , item in ipairs ( list ) do
if item.valid == 1 then
local lnum = math.max ( 0 , item.lnum - 1 )
2021-11-18 12:27:46 -07:00
local col = math.max ( 0 , item.col - 1 )
2021-09-19 15:13:23 -07:00
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
2024-01-09 05:47:57 -07:00
diagnostics [ # diagnostics + 1 ] = {
2021-09-19 15:13:23 -07:00
bufnr = item.bufnr ,
lnum = lnum ,
col = col ,
end_lnum = end_lnum ,
end_col = end_col ,
severity = severity ,
message = item.text ,
2024-01-09 05:47:57 -07:00
}
2021-09-19 15:13:23 -07:00
end
end
return diagnostics
end
2021-09-06 19:21:18 -07:00
return M