commit 09266830bdf410c27be49a7993ad60bd9b4bae6c
parent 0790e08f52a9606eaadc70c1f2825fc427d979ed
Author: Yochem van Rosmalen <git@yochem.nl>
Date: Mon, 22 Sep 2025 22:33:16 +0200
fix(diagnostic): show diagnostics on buffer load #35866
Problem:
Diagnostics set on unloaded buffers are not shown when the buffer loads.
Solution:
Use `once_buf_loaded()` to show the diagnostics on BufRead is the buffer
is not yet loaded.
Diffstat:
1 file changed, 146 insertions(+), 149 deletions(-)
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
@@ -1636,45 +1636,43 @@ M.handlers.signs = {
bufnr = vim._resolve_bufnr(bufnr)
opts = opts or {}
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
-
- -- 10 is the default sign priority when none is explicitly specified
- local priority = opts.signs and opts.signs.priority or 10
- local get_priority = severity_to_extmark_priority(priority, opts)
-
- local ns = M.get_namespace(namespace)
- if not ns.user_data.sign_ns then
- ns.user_data.sign_ns =
- api.nvim_create_namespace(string.format('nvim.%s.diagnostic.signs', ns.name))
- end
+ once_buf_loaded(bufnr, function()
+ -- 10 is the default sign priority when none is explicitly specified
+ local priority = opts.signs and opts.signs.priority or 10
+ local get_priority = severity_to_extmark_priority(priority, opts)
+
+ local ns = M.get_namespace(namespace)
+ if not ns.user_data.sign_ns then
+ ns.user_data.sign_ns =
+ api.nvim_create_namespace(string.format('nvim.%s.diagnostic.signs', ns.name))
+ end
- 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]
- elseif type(k) == 'string' and not text[k] then
- text[k] = k:sub(1, 1):upper()
+ 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]
+ elseif type(k) == 'string' and not text[k] then
+ text[k] = k:sub(1, 1):upper()
+ end
end
- end
- local numhl = opts.signs.numhl or {}
- local linehl = opts.signs.linehl or {}
+ local numhl = opts.signs.numhl or {}
+ local linehl = opts.signs.linehl or {}
- local line_count = api.nvim_buf_line_count(bufnr)
+ local line_count = api.nvim_buf_line_count(bufnr)
- for _, diagnostic in ipairs(diagnostics) do
- if diagnostic.lnum <= line_count 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],
- number_hl_group = numhl[diagnostic.severity],
- line_hl_group = linehl[diagnostic.severity],
- priority = get_priority(diagnostic.severity),
- })
+ for _, diagnostic in ipairs(diagnostics) do
+ if diagnostic.lnum <= line_count 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],
+ number_hl_group = numhl[diagnostic.severity],
+ line_hl_group = linehl[diagnostic.severity],
+ priority = get_priority(diagnostic.severity),
+ })
+ end
end
- end
+ end)
end,
--- @param namespace integer
@@ -1697,45 +1695,43 @@ M.handlers.underline = {
bufnr = vim._resolve_bufnr(bufnr)
opts = opts or {}
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
-
- local ns = M.get_namespace(namespace)
- if not ns.user_data.underline_ns then
- ns.user_data.underline_ns =
- api.nvim_create_namespace(string.format('nvim.%s.diagnostic.underline', ns.name))
- end
+ once_buf_loaded(bufnr, function()
+ local ns = M.get_namespace(namespace)
+ if not ns.user_data.underline_ns then
+ ns.user_data.underline_ns =
+ api.nvim_create_namespace(string.format('nvim.%s.diagnostic.underline', ns.name))
+ end
- local underline_ns = ns.user_data.underline_ns
- local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts)
+ local underline_ns = ns.user_data.underline_ns
+ local get_priority = severity_to_extmark_priority(vim.hl.priorities.diagnostics, opts)
- for _, diagnostic in ipairs(diagnostics) do
- local higroup = underline_highlight_map[diagnostic.severity]
+ for _, diagnostic in ipairs(diagnostics) do
+ local higroup = underline_highlight_map[diagnostic.severity]
- 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'
+ 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
- end
-
- local lines =
- api.nvim_buf_get_lines(diagnostic.bufnr, diagnostic.lnum, diagnostic.lnum + 1, true)
- vim.hl.range(
- bufnr,
- underline_ns,
- higroup,
- { diagnostic.lnum, math.min(diagnostic.col, #lines[1] - 1) },
- { diagnostic.end_lnum, diagnostic.end_col },
- { priority = get_priority(diagnostic.severity) }
- )
- end
- save_extmarks(underline_ns, bufnr)
+ local lines =
+ api.nvim_buf_get_lines(diagnostic.bufnr, diagnostic.lnum, diagnostic.lnum + 1, true)
+
+ vim.hl.range(
+ bufnr,
+ underline_ns,
+ higroup,
+ { diagnostic.lnum, math.min(diagnostic.col, #lines[1] - 1) },
+ { diagnostic.end_lnum, diagnostic.end_col },
+ { priority = get_priority(diagnostic.severity) }
+ )
+ end
+ save_extmarks(underline_ns, bufnr)
+ end)
end,
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)
@@ -1795,51 +1791,54 @@ M.handlers.virtual_text = {
bufnr = vim._resolve_bufnr(bufnr)
opts = opts or {}
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
+ once_buf_loaded(bufnr, function()
+ if opts.virtual_text then
+ if opts.virtual_text.format then
+ diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
+ end
+ if
+ opts.virtual_text.source
+ and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1)
+ then
+ diagnostics = prefix_source(diagnostics)
+ end
+ end
- if opts.virtual_text then
- if opts.virtual_text.format then
- diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
+ local ns = M.get_namespace(namespace)
+ if not ns.user_data.virt_text_ns then
+ ns.user_data.virt_text_ns =
+ api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_text', ns.name))
end
- if
- opts.virtual_text.source
- and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1)
- then
- diagnostics = prefix_source(diagnostics)
+ if not ns.user_data.virt_text_augroup then
+ ns.user_data.virt_text_augroup = api.nvim_create_augroup(
+ string.format('nvim.%s.diagnostic.virt_text', ns.name),
+ { clear = true }
+ )
end
- end
- local ns = M.get_namespace(namespace)
- if not ns.user_data.virt_text_ns then
- ns.user_data.virt_text_ns =
- api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_text', ns.name))
- end
- if not ns.user_data.virt_text_augroup then
- ns.user_data.virt_text_augroup = api.nvim_create_augroup(
- string.format('nvim.%s.diagnostic.virt_text', ns.name),
- { clear = true }
- )
- end
+ api.nvim_clear_autocmds({ group = ns.user_data.virt_text_augroup, buffer = bufnr })
- api.nvim_clear_autocmds({ group = ns.user_data.virt_text_augroup, buffer = bufnr })
-
- local line_diagnostics = diagnostic_lines(diagnostics, true)
+ local line_diagnostics = diagnostic_lines(diagnostics, true)
- if opts.virtual_text.current_line ~= nil then
- api.nvim_create_autocmd('CursorMoved', {
- buffer = bufnr,
- group = ns.user_data.virt_text_augroup,
- callback = function()
- render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text)
- end,
- })
- end
+ if opts.virtual_text.current_line ~= nil then
+ api.nvim_create_autocmd('CursorMoved', {
+ buffer = bufnr,
+ group = ns.user_data.virt_text_augroup,
+ callback = function()
+ render_virtual_text(
+ ns.user_data.virt_text_ns,
+ bufnr,
+ line_diagnostics,
+ opts.virtual_text
+ )
+ end,
+ })
+ end
- render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text)
+ render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text)
- save_extmarks(ns.user_data.virt_text_ns, bufnr)
+ save_extmarks(ns.user_data.virt_text_ns, bufnr)
+ end)
end,
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)
@@ -2054,53 +2053,51 @@ M.handlers.virtual_lines = {
bufnr = vim._resolve_bufnr(bufnr)
opts = opts or {}
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
-
- local ns = M.get_namespace(namespace)
- if not ns.user_data.virt_lines_ns then
- ns.user_data.virt_lines_ns =
- api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_lines', ns.name))
- end
- if not ns.user_data.virt_lines_augroup then
- ns.user_data.virt_lines_augroup = api.nvim_create_augroup(
- string.format('nvim.%s.diagnostic.virt_lines', ns.name),
- { clear = true }
- )
- end
-
- api.nvim_clear_autocmds({ group = ns.user_data.virt_lines_augroup, buffer = bufnr })
-
- diagnostics =
- reformat_diagnostics(opts.virtual_lines.format or format_virtual_lines, diagnostics)
+ once_buf_loaded(bufnr, function()
+ local ns = M.get_namespace(namespace)
+ if not ns.user_data.virt_lines_ns then
+ ns.user_data.virt_lines_ns =
+ api.nvim_create_namespace(string.format('nvim.%s.diagnostic.virtual_lines', ns.name))
+ end
+ if not ns.user_data.virt_lines_augroup then
+ ns.user_data.virt_lines_augroup = api.nvim_create_augroup(
+ string.format('nvim.%s.diagnostic.virt_lines', ns.name),
+ { clear = true }
+ )
+ end
- if opts.virtual_lines.current_line == true then
- -- Create a mapping from line -> diagnostics so that we can quickly get the
- -- diagnostics we need when the cursor line doesn't change.
- local line_diagnostics = diagnostic_lines(diagnostics, true)
- api.nvim_create_autocmd('CursorMoved', {
- buffer = bufnr,
- group = ns.user_data.virt_lines_augroup,
- callback = function()
- render_virtual_lines(
- ns.user_data.virt_lines_ns,
- bufnr,
- diagnostics_at_cursor(line_diagnostics)
- )
- end,
- })
- -- Also show diagnostics for the current line before the first CursorMoved event.
- render_virtual_lines(
- ns.user_data.virt_lines_ns,
- bufnr,
- diagnostics_at_cursor(line_diagnostics)
- )
- else
- render_virtual_lines(ns.user_data.virt_lines_ns, bufnr, diagnostics)
- end
+ api.nvim_clear_autocmds({ group = ns.user_data.virt_lines_augroup, buffer = bufnr })
+
+ diagnostics =
+ reformat_diagnostics(opts.virtual_lines.format or format_virtual_lines, diagnostics)
+
+ if opts.virtual_lines.current_line == true then
+ -- Create a mapping from line -> diagnostics so that we can quickly get the
+ -- diagnostics we need when the cursor line doesn't change.
+ local line_diagnostics = diagnostic_lines(diagnostics, true)
+ api.nvim_create_autocmd('CursorMoved', {
+ buffer = bufnr,
+ group = ns.user_data.virt_lines_augroup,
+ callback = function()
+ render_virtual_lines(
+ ns.user_data.virt_lines_ns,
+ bufnr,
+ diagnostics_at_cursor(line_diagnostics)
+ )
+ end,
+ })
+ -- Also show diagnostics for the current line before the first CursorMoved event.
+ render_virtual_lines(
+ ns.user_data.virt_lines_ns,
+ bufnr,
+ diagnostics_at_cursor(line_diagnostics)
+ )
+ else
+ render_virtual_lines(ns.user_data.virt_lines_ns, bufnr, diagnostics)
+ end
- save_extmarks(ns.user_data.virt_lines_ns, bufnr)
+ save_extmarks(ns.user_data.virt_lines_ns, bufnr)
+ end)
end,
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)