Files
neovim/runtime/lua/vim/diagnostic/_display.lua
Justin M. Keyes 9aa4608401 refactor: introduce nvim_on internally #39883
Problem:
`nvim_create_autocmd` is too verbose and its `callback` requires extra
"nesting".

Solution:
Introduce `nvim_on`. Start using it internally. Then we can get a feel
for how it should look before making it public.
2026-05-20 17:33:01 -04:00

193 lines
6.4 KiB
Lua

local api = vim.api
local nvim_on = require('vim._core.util').nvim_on
local diagnostic_modules = vim._defer_require('vim.diagnostic', {
_config = ..., --- @module 'vim.diagnostic._config'
_severity = ..., --- @module 'vim.diagnostic._severity'
_shared = ..., --- @module 'vim.diagnostic._shared'
_store = ..., --- @module 'vim.diagnostic._store'
})
--- @class (private) vim.diagnostic._display
local M = {}
-- 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] = {}
return t[bufnr]
end,
}
--- @type table<integer,table<integer,table>>
local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
--- @type table<string,true>
local registered_autocmds = {}
--- 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
--- @return string
local function make_augroup_key(namespace, bufnr)
local ns = vim.diagnostic.get_namespace(namespace)
return string.format('nvim.diagnostic.insertleave.%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
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
--- @param namespace integer
--- @param bufnr integer
--- @param args vim.diagnostic.OptsResolved
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
local group = api.nvim_create_augroup(key, { clear = true })
nvim_on(insert_leave_auto_cmds, group, {
buf = bufnr,
desc = 'vim.diagnostic: display diagnostics',
}, function()
execute_scheduled_display(namespace, bufnr)
end)
registered_autocmds[key] = true
end
end
--- @param namespace integer
--- @param bufnr integer
local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr)
if registered_autocmds[key] then
api.nvim_del_augroup_by_name(key)
registered_autocmds[key] = nil
end
end
--- @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, 'number', true)
vim.validate('bufnr', bufnr, 'number', true)
local buffers = bufnr and { vim._resolve_bufnr(bufnr) } or diagnostic_modules._store.get_bufnrs()
for _, iter_bufnr in ipairs(buffers) do
local namespaces = namespace and { namespace }
or diagnostic_modules._store.get_buf_namespaces(iter_bufnr)
for _, iter_namespace in ipairs(namespaces) do
for _, handler in pairs(vim.diagnostic.handlers) do
if handler.hide then
handler.hide(iter_namespace, iter_bufnr)
end
end
end
end
end
--- @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? vim.diagnostic.Opts Display options.
function M.show(namespace, bufnr, diagnostics, opts)
vim.validate('namespace', namespace, 'number', true)
vim.validate('bufnr', bufnr, 'number', true)
vim.validate('diagnostics', diagnostics, vim.islist, true, 'a list of diagnostics')
vim.validate('opts', opts, 'table', true)
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 ipairs(diagnostic_modules._store.get_bufnrs()) do
M.show(namespace, iter_bufnr, nil, opts)
end
else
-- namespace is nil
bufnr = vim._resolve_bufnr(bufnr)
for _, iter_namespace in ipairs(diagnostic_modules._store.get_buf_namespaces(bufnr)) do
M.show(iter_namespace, bufnr, nil, opts)
end
end
return
end
if not vim.diagnostic.is_enabled({ bufnr = bufnr or 0, ns_id = namespace }) then
return
end
M.hide(namespace, bufnr)
diagnostics = diagnostics
or diagnostic_modules._store.get_diagnostics(bufnr, {
namespace = namespace,
}, true)
if vim.tbl_isempty(diagnostics) then
return
end
local opts_res = diagnostic_modules._config.get_resolved_options(opts, namespace, bufnr)
if opts_res.update_in_insert then
clear_scheduled_display(namespace, bufnr)
else
local mode = api.nvim_get_mode()
if mode.mode:sub(1, 1) == 'i' then
schedule_display(namespace, bufnr, opts_res)
return
end
end
if opts_res.severity_sort then
if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then
table.sort(diagnostics, function(a, b)
return diagnostic_modules._shared.diagnostic_cmp(a, b, 'severity', false)
end)
else
table.sort(diagnostics, function(a, b)
return diagnostic_modules._shared.diagnostic_cmp(a, b, 'severity', true)
end)
end
end
for handler_name, handler in pairs(vim.diagnostic.handlers) do
if handler.show and opts_res[handler_name] then
local filtered = diagnostic_modules._severity.filter_by_severity(
opts_res[handler_name].severity,
diagnostics
)
handler.show(namespace, bufnr, filtered, opts_res)
end
end
end
return M