neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 01666aae64d15d88896f99bc064c7b2b4c2ed276
parent 0864939cc58d3f0af1a88a4baf34ed6b2a2c53ed
Author: Dmytro Pletenskyi <32138582+ph1losof@users.noreply.github.com>
Date:   Sat, 14 Feb 2026 12:07:01 +0100

feat(diagnostic): fromqflist({merge_lines}) #37416

Problem:
`vim.diagnostic.fromqflist` ignores lines that are `item.valid == 0` (see
`getqflist`). Many qflists have messages that span multiple lines, which look
like this:

    collection/src/Modelling/CdOd/Central.hs|496 col 80| error: [GHC-83865]
    ||     • Couldn't match expected type: InstanceWithForm
    ||                                       (FilePath
    ||                                        -> SelectValidCdInstWithForm
    ...

calling `vim.diagnostic.fromqflist(vim.fn.getqflist)` gets a diagnostic message
like this:

    error: [GHC-83865]

only the first line is kept, but often, the remaing lines are useful as well.

Solution:
Introduce `merge_lines` option, which "squashes" lines from invalid qflist items
into the error message of the previous valid item, so that we get this
diagnostic message instead:

    error: [GHC-83865]
         • Couldn't match expected type: InstanceWithForm
                                           (FilePath
                                            -> SelectValidCdInstWithForm

Diffstat:
Mruntime/doc/diagnostic.txt | 10+++++++---
Mruntime/doc/news.txt | 2++
Mruntime/lua/vim/diagnostic.lua | 23++++++++++++++++++++---
Mtest/functional/lua/diagnostic_spec.lua | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 121 insertions(+), 6 deletions(-)

diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt @@ -802,12 +802,16 @@ enable({enable}, {filter}) *vim.diagnostic.enable()* • {bufnr}? (`integer`) Buffer number, or 0 for current buffer, or `nil` for all buffers. -fromqflist({list}) *vim.diagnostic.fromqflist()* +fromqflist({list}, {opts}) *vim.diagnostic.fromqflist()* Convert a list of quickfix items to a list of diagnostics. Parameters: ~ - • {list} (`table[]`) List of quickfix items from |getqflist()| or - |getloclist()|. + • {list} (`vim.quickfix.entry[]`) List of quickfix items from + |getqflist()| or |getloclist()|. + • {opts} (`table?`) Configuration table with the following keys: + • {merge_lines}? (`boolean`) When true, items with valid=0 are + appended to the previous valid item's message with a + newline. (default: false) Return: ~ (`vim.Diagnostic[]`) See |vim.Diagnostic|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt @@ -198,6 +198,8 @@ DIAGNOSTICS enabled or disabled diagnostics. • |vim.diagnostic.status()| returns a formatted string with current buffer diagnostics +• |vim.diagnostic.fromqflist()| now accepts an `opts` table with + `merge_lines` to merge multi-line compiler messages. EDITOR diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua @@ -2913,14 +2913,27 @@ function M.toqflist(diagnostics) return list end +--- Configuration table with the following keys: +--- @class vim.diagnostic.fromqflist.Opts +--- @inlinedoc +--- +--- When true, items with valid=0 are appended to the previous valid item's +--- message with a newline. (default: false) +--- @field merge_lines? boolean + --- Convert a list of quickfix items to a list of diagnostics. --- ----@param list table[] List of quickfix items from |getqflist()| or |getloclist()|. +---@param list vim.quickfix.entry[] List of quickfix items from |getqflist()| or |getloclist()|. +---@param opts? vim.diagnostic.fromqflist.Opts ---@return vim.Diagnostic[] -function M.fromqflist(list) +function M.fromqflist(list, opts) vim.validate('list', list, 'table') + opts = opts or {} + local merge = opts.merge_lines + local diagnostics = {} --- @type vim.Diagnostic[] + local last_diag --- @type vim.Diagnostic? for _, item in ipairs(list) do if item.valid == 1 then local lnum = math.max(0, item.lnum - 1) @@ -2929,7 +2942,7 @@ function M.fromqflist(list) local end_col = item.end_col > 0 and (item.end_col - 1) or col local code = item.nr > 0 and item.nr or nil local severity = item.type ~= '' and M.severity[item.type:upper()] or M.severity.ERROR - diagnostics[#diagnostics + 1] = { + local diag = { bufnr = item.bufnr, lnum = lnum, col = col, @@ -2939,6 +2952,10 @@ function M.fromqflist(list) message = item.text, code = code, } + diagnostics[#diagnostics + 1] = diag + last_diag = diag + elseif merge and last_diag then + last_diag.message = last_diag.message .. '\n' .. item.text end end return diagnostics diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua @@ -4089,6 +4089,98 @@ describe('vim.diagnostic', function() end) eq(result[1], result[2]) end) + + it('merge_lines=true merges continuation lines', function() + local result = exec_lua(function() + local qflist = { + { + bufnr = 1, + lnum = 10, + col = 5, + end_lnum = 10, + end_col = 10, + text = 'error: [GHC-83865]', + type = 'E', + nr = 0, + valid = 1, + }, + { + bufnr = 1, + lnum = 0, + col = 0, + end_lnum = 0, + end_col = 0, + text = " Couldn't match expected type", + type = '', + nr = 0, + valid = 0, + }, + { + bufnr = 1, + lnum = 0, + col = 0, + end_lnum = 0, + end_col = 0, + text = ' with actual type', + type = '', + nr = 0, + valid = 0, + }, + { + bufnr = 1, + lnum = 20, + col = 1, + end_lnum = 20, + end_col = 5, + text = 'warning: unused', + type = 'W', + nr = 0, + valid = 1, + }, + } + return vim.diagnostic.fromqflist(qflist, { merge_lines = true }) + end) + + eq(2, #result) + eq( + "error: [GHC-83865]\n Couldn't match expected type\n with actual type", + result[1].message + ) + eq('warning: unused', result[2].message) + end) + + it('merge_lines=false ignores continuation lines', function() + local result = exec_lua(function() + local qflist = { + { + bufnr = 1, + lnum = 10, + col = 5, + end_lnum = 10, + end_col = 10, + text = 'error: main', + type = 'E', + nr = 0, + valid = 1, + }, + { + bufnr = 1, + lnum = 0, + col = 0, + end_lnum = 0, + end_col = 0, + text = 'continuation', + type = '', + nr = 0, + valid = 0, + }, + } + return vim.diagnostic.fromqflist(qflist) + end) + + eq(1, #result) + eq('error: main', result[1].message) + end) end) describe('status()', function()