Repository: neovim/neovim
File: runtime/lua/vim/diagnostic.lua (Lua)

Code before:
  end
end

--- Execute a given function now if the given buffer is already loaded or once it is loaded later.
---
---@param bufnr integer Buffer number
---@param fn fun()
local function once_buf_loaded(bufnr, fn)
  if api.nvim_buf_is_loaded(bufnr) then
    fn()
  else
    api.nvim_create_autocmd('BufRead', {
      buffer = bufnr,
      once = true,
      callback = function()
        fn()
      end,
    })
  end
end

--- Set diagnostics for the given namespace and buffer.
---
---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number
---@param diagnostics vim.Diagnostic[]
---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
  vim.validate('namespace', namespace, 'number')
  vim.validate('bufnr', bufnr, 'number')
  vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  vim.validate('opts', opts, 'table', true)

  bufnr = vim._resolve_bufnr(bufnr)

  if vim.tbl_isempty(diagnostics) then
    diagnostic_cache[bufnr][namespace] = nil
  else
    set_diagnostic_cache(namespace, bufnr, diagnostics)
  end

  once_buf_loaded(bufnr, function()
    local ns = M.get_namespace(namespace)

    if not ns.user_data.location_ns then
      ns.user_data.location_ns =
        api.nvim_create_namespace(string.format('nvim.%s.diagnostic', ns.name))
    end

    api.nvim_buf_clear_namespace(bufnr, ns.user_data.location_ns, 0, -1)


Review comment (suggestion):
This is useful in particular for `vim.diagnostic.set(vim.diagnostic.fromqflist())` use case.

When using `:make` or similar it's normal to end up with unloaded buffers associated with quickfix items if you didn't edit that buffer before running `:make`, in which case it would not create extmarks and you wouldn't be able to jump between diagnostics.

Builtin diagnostic handlers currently do nothing for unloaded buffers, meaning you won't get underlines, virtual text etc.
Ideally they should also be changed to use this function. I planned to do that in a separate follow-up PR though.

Code after:
  else
    for bufnr, v in pairs(diagnostic_cache) do
      for ns in pairs(v) do
        M.show(ns, bufnr)
      end
    end
  end
end

--- Execute a given function now if the given buffer is already loaded or once it is loaded later.
---
---@param bufnr integer Buffer number
---@param fn fun()
local function once_buf_loaded(bufnr, fn)
  if api.nvim_buf_is_loaded(bufnr) then
    fn()
  else
    api.nvim_create_autocmd('BufRead', {
      buffer = bufnr,
      once = true,
      callback = function()
        fn()
      end,
    })
  end
end

--- Set diagnostics for the given namespace and buffer.
---
---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number
---@param diagnostics vim.Diagnostic.Set[]
---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
  vim.validate('namespace', namespace, 'number')
  vim.validate('bufnr', bufnr, 'number')
  vim.validate('diagnostics', diagnostics, vim.islist, 'a list of diagnostics')
  vim.validate('opts', opts, 'table', true)

  bufnr = vim._resolve_bufnr(bufnr)

  for _, diagnostic in ipairs(diagnostics) do
    norm_diag(bufnr, namespace, diagnostic)
  end

  --- @cast diagnostics vim.Diagnostic[]

  if vim.tbl_isempty(diagnostics) then
    diagnostic_cache[bufnr][namespace] = nil
  else
    diagnostic_cache[bufnr][namespace] = diagnostics