From b13e63db1dbc1dbc7e23690653df1b7317660a2b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 25 Apr 2024 08:07:44 -0500 Subject: [PATCH] feat(diagnostic): goto functions jump to highest severity (#28490) When the "severity" option is nil, vim.diagnostic.goto_next() and vim.diagnostic.goto_prev() jump to the next diagnostic with the highest severity. --- runtime/doc/diagnostic.txt | 5 +- runtime/doc/news.txt | 5 + runtime/lua/vim/diagnostic.lua | 30 ++++- test/functional/lua/diagnostic_spec.lua | 141 ++++++++++++++++++++---- 4 files changed, 154 insertions(+), 27 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index b7d5cb0b2c..32c9959f5a 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -389,8 +389,9 @@ Lua module: vim.diagnostic *diagnostic-api* |nvim_win_get_cursor()|. • {wrap}? (`boolean`, default: `true`) Whether to loop around file or not. Similar to 'wrapscan'. - • {severity} (`vim.diagnostic.Severity`) See - |diagnostic-severity|. + • {severity}? (`vim.diagnostic.Severity`) See + |diagnostic-severity|. If `nil`, go to the + diagnostic with the highest severity. • {float}? (`boolean|vim.diagnostic.Opts.Float`, default: `true`) If `true`, call |vim.diagnostic.open_float()| after moving. If a diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 537542ee46..44d5ea39bf 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -387,6 +387,11 @@ The following changes to existing APIs or features add new behavior. • |vim.diagnostic.config()| now accepts a function for the virtual_text.prefix option, which allows for rendering e.g., diagnostic severities differently. +• |vim.diagnostic.goto_next()| and |vim.diagnostic.goto_prev()| jump to the + diagnostic with the highest severity when the "severity" option is + unspecified. To use the old behavior, use: >lua + vim.diagnostic.goto_next({ severity = { min = vim.diagnostic.severity.HINT } }) + • Defaults: • On Windows 'isfname' does not include ":". Drive letters are handled correctly without it. (Use |gF| for filepaths suffixed with ":line:col"). diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 3321b6ad71..1ae370e9b2 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -820,11 +820,35 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 bufnr = get_bufnr(bufnr) local wrap = if_nil(opts.wrap, true) - local line_count = api.nvim_buf_line_count(bufnr) + local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) + + -- When severity is unset we jump to the diagnostic with the highest severity. First sort the + -- diagnostics by severity. The first diagnostic then contains the highest severity, and we can + -- discard all diagnostics with a lower severity. + if opts.severity == nil then + table.sort(diagnostics, function(a, b) + return a.severity < b.severity + end) + + -- Find the first diagnostic where the severity does not match the highest severity, and remove + -- that element and all subsequent elements from the array + local worst = (diagnostics[1] or {}).severity + local len = #diagnostics + for i = 2, len do + if diagnostics[i].severity ~= worst then + for j = i, len do + diagnostics[j] = nil + end + break + end + end + end + local line_diagnostics = diagnostic_lines(diagnostics) + local line_count = api.nvim_buf_line_count(bufnr) for i = 0, line_count do local offset = i * (search_forward and 1 or -1) local lnum = position[1] + offset @@ -1165,8 +1189,8 @@ end --- (default: `true`) --- @field wrap? boolean --- ---- See |diagnostic-severity|. ---- @field severity vim.diagnostic.Severity +--- See |diagnostic-severity|. If `nil`, go to the diagnostic with the highest severity. +--- @field severity? vim.diagnostic.Severity --- --- If `true`, call |vim.diagnostic.open_float()| after moving. --- If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|. diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index f36a676ba9..534d78fd09 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -18,12 +18,12 @@ describe('vim.diagnostic', function() exec_lua [[ require('vim.diagnostic') - function make_diagnostic(msg, x1, y1, x2, y2, severity, source, code) + function make_diagnostic(msg, lnum, col, end_lnum, end_col, severity, source, code) return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, + lnum = lnum, + col = col, + end_lnum = end_lnum, + end_col = end_col, message = msg, severity = severity, source = source, @@ -31,20 +31,20 @@ describe('vim.diagnostic', function() } end - function make_error(msg, x1, y1, x2, y2, source, code) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source, code) + function make_error(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.ERROR, source, code) end - function make_warning(msg, x1, y1, x2, y2, source, code) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source, code) + function make_warning(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.WARN, source, code) end - function make_info(msg, x1, y1, x2, y2, source, code) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source, code) + function make_info(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.INFO, source, code) end - function make_hint(msg, x1, y1, x2, y2, source, code) - return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source, code) + function make_hint(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.HINT, source, code) end function count_diagnostics(bufnr, severity, namespace) @@ -934,15 +934,112 @@ describe('vim.diagnostic', function() eq( { 4, 0 }, exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 3, 9001, 3, 9001), - make_error('Diagnostic #2', 4, -1, 4, -1), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 1}) - vim.diagnostic.goto_next { float = false } - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } - ]] + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 3, 9001, 3, 9001), + make_error('Diagnostic #2', 4, -1, 4, -1), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 1}) + vim.diagnostic.goto_next { float = false } + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } + ]] + ) + end) + + it('jumps to diagnostic with highest severity', function() + exec_lua([[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_info('Info', 1, 0, 1, 1), + make_error('Error', 2, 0, 2, 1), + make_warning('Warning', 3, 0, 3, 1), + make_error('Error', 4, 0, 4, 1), + }) + + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 0}) + ]]) + + eq( + { 3, 0 }, + exec_lua([[ + vim.diagnostic.goto_next() + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + + eq( + { 5, 0 }, + exec_lua([[ + vim.diagnostic.goto_next() + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + + exec_lua([[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_info('Info', 1, 0, 1, 1), + make_hint('Hint', 2, 0, 2, 1), + make_warning('Warning', 3, 0, 3, 1), + make_hint('Hint', 4, 0, 4, 1), + make_warning('Warning', 5, 0, 5, 1), + }) + + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 0}) + ]]) + + eq( + { 4, 0 }, + exec_lua([[ + vim.diagnostic.goto_next() + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + + eq( + { 6, 0 }, + exec_lua([[ + vim.diagnostic.goto_next() + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + end) + + it('jumps to next diagnostic if severity is non-nil', function() + exec_lua([[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_info('Info', 1, 0, 1, 1), + make_error('Error', 2, 0, 2, 1), + make_warning('Warning', 3, 0, 3, 1), + make_error('Error', 4, 0, 4, 1), + }) + + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 0}) + ]]) + + eq( + { 2, 0 }, + exec_lua([[ + vim.diagnostic.goto_next({ severity = { min = vim.diagnostic.severity.HINT } }) + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + + eq( + { 3, 0 }, + exec_lua([[ + vim.diagnostic.goto_next({ severity = { min = vim.diagnostic.severity.HINT } }) + return vim.api.nvim_win_get_cursor(0) + ]]) + ) + + eq( + { 4, 0 }, + exec_lua([[ + vim.diagnostic.goto_next({ severity = { min = vim.diagnostic.severity.HINT } }) + return vim.api.nvim_win_get_cursor(0) + ]]) ) end) end)