diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 59b73771a6..5845f11073 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -47,6 +47,7 @@ A diagnostic is a Lua table with the following keys: end_col: The final column of the diagnostic severity: The severity of the diagnostic |vim.diagnostic.severity| message: The diagnostic text + source: The source of the diagnostic Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based rows and columns). |api-indexing| @@ -226,6 +227,9 @@ config({opts}, {namespace}) *vim.diagnostic.config()* • severity: Only show virtual text for diagnostics matching the given severity |diagnostic-severity| + • source: (string) Include the diagnostic + source in virtual text. One of "always" + or "if_many". • signs: (default true) Use signs for diagnostics. Options: @@ -532,6 +536,9 @@ show_position_diagnostics({opts}, {bufnr}, {position}) • severity: See |diagnostic-severity|. • show_header: (boolean, default true) Show "Diagnostics:" header + • source: (string) Include the diagnostic + source in the message. One of "always" or + "if_many". {bufnr} number|nil Buffer number. Defaults to the current buffer. {position} table|nil The (0,0)-indexed position. Defaults diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 0261475f72..26ee9ebca5 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -47,6 +47,35 @@ local function filter_by_severity(severity, diagnostics) return vim.tbl_filter(function(t) return t.severity <= min_severity and t.severity >= max_severity end, diagnostics) end +---@private +local function prefix_source(source, diagnostics) + vim.validate { source = {source, function(v) + return v == "always" or v == "if_many" + end, "Invalid value for option 'source'" } } + + if source == "if_many" then + local sources = {} + for _, d in pairs(diagnostics) do + if d.source then + sources[d.source] = true + end + end + if #vim.tbl_keys(sources) <= 1 then + return diagnostics + end + end + + return vim.tbl_map(function(d) + if not d.source then + return d + end + + local t = vim.deepcopy(d) + t.message = string.format("%s: %s", d.source, d.message) + return t + end, diagnostics) +end + ---@private local function resolve_optional_value(option, namespace, bufnr) local enabled_val = {} @@ -336,7 +365,9 @@ end ---@param diagnostics table: The diagnostics to display ---@return table {popup_bufnr, win_id} local function show_diagnostics(opts, diagnostics) - if vim.tbl_isempty(diagnostics) then return end + if vim.tbl_isempty(diagnostics) then + return + end local lines = {} local highlights = {} local show_header = vim.F.if_nil(opts.show_header, true) @@ -345,6 +376,10 @@ local function show_diagnostics(opts, diagnostics) table.insert(highlights, {0, "Bold"}) end + if opts.source then + diagnostics = prefix_source(opts.source, diagnostics) + end + for i, diagnostic in ipairs(diagnostics) do local prefix = string.format("%d. ", i) local hiname = floating_highlight_map[diagnostic.severity] @@ -487,6 +522,8 @@ end --- - virtual_text: (default true) Use virtual text for diagnostics. Options: --- * severity: Only show virtual text for diagnostics matching the given --- severity |diagnostic-severity| +--- * source: (string) Include the diagnostic source in virtual +--- text. One of "always" or "if_many". --- - signs: (default true) Use signs for diagnostics. Options: --- * severity: Only show signs for diagnostics matching the given severity --- |diagnostic-severity| @@ -826,6 +863,10 @@ function M._set_virtual_text(namespace, bufnr, diagnostics, opts) bufnr = get_bufnr(bufnr) opts = get_resolved_options({ virtual_text = opts }, namespace, bufnr).virtual_text + if opts and opts.source then + diagnostics = prefix_source(opts.source, diagnostics) + end + local buffer_line_diagnostics = diagnostic_lines(diagnostics) for line, line_diagnostics in pairs(buffer_line_diagnostics) do if opts and opts.severity then @@ -1007,6 +1048,8 @@ end --- - namespace: (number) Limit diagnostics to the given namespace --- - severity: See |diagnostic-severity|. --- - show_header: (boolean, default true) Show "Diagnostics:" header +--- - source: (string) Include the diagnostic source in +--- the message. One of "always" or "if_many". ---@param bufnr number|nil Buffer number. Defaults to the current buffer. ---@param position table|nil The (0,0)-indexed position. Defaults to the current cursor position. ---@return tuple ({popup_bufnr}, {win_id}) diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index a9d93ae65f..b4557b0e26 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -103,6 +103,7 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding), severity = severity_lsp_to_vim(diagnostic.severity), message = diagnostic.message, + source = diagnostic.source, user_data = { lsp = { code = diagnostic.code, @@ -132,6 +133,7 @@ local function diagnostic_vim_to_lsp(diagnostics) }, severity = severity_vim_to_lsp(diagnostic.severity), message = diagnostic.message, + source = diagnostic.source, }, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {}) end, diagnostics) end diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index e542b02abe..7c5a52827c 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -14,48 +14,32 @@ describe('vim.diagnostic', function() exec_lua [[ require('vim.diagnostic') - function make_error(msg, x1, y1, x2, y2) + function make_diagnostic(msg, x1, y1, x2, y2, severity, source) return { lnum = x1, col = y1, end_lnum = x2, end_col = y2, message = msg, - severity = vim.diagnostic.severity.ERROR, + severity = severity, + source = source, } end - function make_warning(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.WARN, - } + function make_error(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source) end - function make_info(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.INFO, - } + function make_warning(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source) end - function make_hint(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.HINT, - } + function make_info(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source) + end + + function make_hint(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source) end function count_diagnostics(bufnr, severity, namespace) @@ -590,6 +574,63 @@ describe('vim.diagnostic', function() eq({'Error', 'Warn', 'Info'}, result[1]) eq({'Info', 'Warn', 'Error'}, result[2]) end) + + it('can show diagnostic sources in virtual text', function() + local result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'always', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' source x: Some error', result) + + result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }, diagnostic_ns) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' Some error', result) + + result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + make_error('Another error', 1, 1, 1, 1, 'source y'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = {extmarks[1][4].virt_text[2][1], extmarks[2][4].virt_text[2][1]} + return virt_text + ]] + eq(' source x: Some error', result[1]) + eq(' source y: Another error', result[2]) + end) end) describe('set()', function() @@ -854,6 +895,49 @@ describe('vim.diagnostic', function() return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) ]]) end) + + it('can show diagnostic source', function() + exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + + eq({"1. Syntax error"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, "source x"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "if_many", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. source x: Syntax error"}, exec_lua [[ + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "always", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. source x: Syntax error", "2. source y: Another error"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, "source x"), + make_error("Another error", 0, 1, 0, 3, "source y"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "if_many", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) end) describe('set_signs()', function()