From d567f899efb70b6e4934bd7bdb938a4e55a3da89 Mon Sep 17 00:00:00 2001 From: Michael Clayton Date: Thu, 1 May 2025 06:54:39 -0400 Subject: [PATCH] fix(diagnostic): allow virtual_{lines,text} cursor exclusivity #33517 Problem: virtual_text diagnostics are great when skimming a file, and virtual_lines are great when "zooming in" on a particular problem. Having both enabled results in duplicate diagnostics on-screen. Solution: This PR expands the behavior of `current_line` for virtual_text and virtual_lines by making `virtual_text.current_line = false` distinct from `nil`. If you set: vim.diagnostic.config({ virtual_text = { current_line = false }, virtual_lines = { current_line = true }, }) With this configuration, virtual_text will be used to display diagnostics until the cursor reaches the same line, at which point they will be hidden and virtual_lines will take its place. --- runtime/doc/diagnostic.txt | 8 ++++-- runtime/lua/vim/diagnostic.lua | 33 +++++++++---------------- test/functional/lua/diagnostic_spec.lua | 19 ++++++++++++++ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index efdfd2bfc6..0282506965 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -624,8 +624,12 @@ Lua module: vim.diagnostic *diagnostic-api* • {severity}? (`vim.diagnostic.SeverityFilter`) Only show virtual text for diagnostics matching the given severity |diagnostic-severity| - • {current_line}? (`boolean`) Only show diagnostics for the - current line. (default `false`) + • {current_line}? (`boolean`) Show or hide diagnostics based on + the current cursor line. If `true`, only + diagnostics on the current cursor line are + shown. If `false`, all diagnostics are shown + except on the current cursor line. If `nil`, all + diagnostics are shown. (default `nil`) • {source}? (`boolean|"if_many"`) Include the diagnostic source in virtual text. Use `'if_many'` to only show sources if there is more than one diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 6e91a27512..6b8d7a6353 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -190,8 +190,10 @@ end --- severity |diagnostic-severity| --- @field severity? vim.diagnostic.SeverityFilter --- ---- Only show diagnostics for the current line. ---- (default `false`) +--- Show or hide diagnostics based on the current cursor line. If `true`, only diagnostics on the +--- current cursor line are shown. If `false`, all diagnostics are shown except on the current +--- cursor line. If `nil`, all diagnostics are shown. +--- (default `nil`) --- @field current_line? boolean --- --- Include the diagnostic source in virtual text. Use `'if_many'` to only @@ -1562,12 +1564,15 @@ M.handlers.underline = { --- @param diagnostics table --- @param opts vim.diagnostic.Opts.VirtualText local function render_virtual_text(namespace, bufnr, diagnostics, opts) + local lnum = api.nvim_win_get_cursor(0)[1] - 1 api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) for line, line_diagnostics in pairs(diagnostics) do local virt_texts = M._get_virt_text_chunks(line_diagnostics, opts) + local skip = (opts.current_line == true and line ~= lnum) + or (opts.current_line == false and line == lnum) - if virt_texts then + if virt_texts and not skip then api.nvim_buf_set_extmark(bufnr, namespace, line, 0, { hl_mode = opts.hl_mode or 'combine', virt_text = virt_texts, @@ -1621,32 +1626,18 @@ M.handlers.virtual_text = { local line_diagnostics = diagnostic_lines(diagnostics) - if opts.virtual_text.current_line == true then + if opts.virtual_text.current_line ~= nil then api.nvim_create_autocmd('CursorMoved', { buffer = bufnr, group = ns.user_data.virt_text_augroup, callback = function() - local lnum = api.nvim_win_get_cursor(0)[1] - 1 - render_virtual_text( - ns.user_data.virt_text_ns, - bufnr, - { [lnum] = diagnostics_at_cursor(line_diagnostics) }, - opts.virtual_text - ) + render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text) end, }) - -- Also show diagnostics for the current line before the first CursorMoved event. - local lnum = api.nvim_win_get_cursor(0)[1] - 1 - render_virtual_text( - ns.user_data.virt_text_ns, - bufnr, - { [lnum] = diagnostics_at_cursor(line_diagnostics) }, - opts.virtual_text - ) - else - render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text) end + render_virtual_text(ns.user_data.virt_text_ns, bufnr, line_diagnostics, opts.virtual_text) + save_extmarks(ns.user_data.virt_text_ns, bufnr) end, hide = function(namespace, bufnr) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 2dba5c2805..c8fb6d9569 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -2144,6 +2144,25 @@ describe('vim.diagnostic', function() eq(1, #result) eq(' Error here!', result[1][4].virt_text[3][1]) end) + + it('can hide virtual_text for the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_text = { current_line = false } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' Another error there!', result[1][4].virt_text[3][1]) + end) end) describe('handlers.virtual_lines', function()