feat(compiler): add configurable error output modes (#14)

Problem: all parse errors went to vim.diagnostic with no way to silence
them or route them to the quickfix list. Users wanting quickfix-style
error navigation had no option.

Solution: add an errors field to ProviderConfig accepting false,
'diagnostic' (default), or 'quickfix'. false suppresses error handling
entirely. 'quickfix' converts parsed diagnostics to qflist items
(1-indexed), calls setqflist, and opens the window. On success,
'quickfix' mode clears the qflist the same way 'diagnostic' mode clears
vim.diagnostic.
This commit is contained in:
Barrett Ruth 2026-03-03 14:57:44 -05:00 committed by GitHub
parent 7995d6422d
commit 253ca05da3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 133 additions and 3 deletions

View file

@ -86,9 +86,18 @@ function M.compile(bufnr, name, provider, ctx)
return
end
local errors_mode = provider.errors
if errors_mode == nil then
errors_mode = 'diagnostic'
end
if result.code == 0 then
log.dbg('compilation succeeded for buffer %d', bufnr)
if errors_mode == 'diagnostic' then
diagnostic.clear(bufnr)
elseif errors_mode == 'quickfix' then
vim.fn.setqflist({}, 'r')
end
vim.api.nvim_exec_autocmds('User', {
pattern = 'PreviewCompileSuccess',
data = { bufnr = bufnr, provider = name, output = output_file },
@ -105,9 +114,27 @@ function M.compile(bufnr, name, provider, ctx)
end
else
log.dbg('compilation failed for buffer %d (exit code %d)', bufnr, result.code)
if provider.error_parser then
if provider.error_parser and errors_mode then
local output = (result.stdout or '') .. (result.stderr or '')
if errors_mode == 'diagnostic' then
diagnostic.set(bufnr, name, provider.error_parser, output, ctx)
elseif errors_mode == 'quickfix' then
local ok, diagnostics = pcall(provider.error_parser, output, ctx)
if ok and diagnostics and #diagnostics > 0 then
local items = {}
for _, d in ipairs(diagnostics) do
table.insert(items, {
bufnr = bufnr,
lnum = d.lnum + 1,
col = d.col + 1,
text = d.message,
type = d.severity == vim.diagnostic.severity.WARN and 'W' or 'E',
})
end
vim.fn.setqflist(items, 'r')
vim.cmd('copen')
end
end
end
vim.api.nvim_exec_autocmds('User', {
pattern = 'PreviewCompileFailed',

View file

@ -6,6 +6,7 @@
---@field env? table<string, string>
---@field output? string|fun(ctx: preview.Context): string
---@field error_parser? fun(output: string, ctx: preview.Context): preview.Diagnostic[]
---@field errors? false|'diagnostic'|'quickfix'
---@field clean? string[]|fun(ctx: preview.Context): string[]
---@field open? boolean|string[]

View file

@ -132,6 +132,108 @@ describe('compiler', function()
end)
end)
describe('errors mode', function()
it('errors = false suppresses error parser', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_errors_false.txt')
vim.bo[bufnr].modified = false
local parser_called = false
local provider = {
cmd = { 'false' },
errors = false,
error_parser = function()
parser_called = true
return {}
end,
}
local ctx = {
bufnr = bufnr,
file = '/tmp/preview_test_errors_false.txt',
root = '/tmp',
ft = 'text',
}
compiler.compile(bufnr, 'falsecmd', provider, ctx)
vim.wait(2000, function()
return compiler._test.active[bufnr] == nil
end, 50)
assert.is_false(parser_called)
helpers.delete_buffer(bufnr)
end)
it('errors = quickfix populates quickfix list', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_errors_qf.txt')
vim.bo[bufnr].modified = false
local provider = {
cmd = { 'sh', '-c', 'echo "line 1 error" >&2; exit 1' },
errors = 'quickfix',
error_parser = function()
return {
{ lnum = 0, col = 0, message = 'test error', severity = vim.diagnostic.severity.ERROR },
}
end,
}
local ctx = {
bufnr = bufnr,
file = '/tmp/preview_test_errors_qf.txt',
root = '/tmp',
ft = 'text',
}
vim.fn.setqflist({}, 'r')
compiler.compile(bufnr, 'qfcmd', provider, ctx)
vim.wait(2000, function()
return compiler._test.active[bufnr] == nil
end, 50)
local qflist = vim.fn.getqflist()
assert.are.equal(1, #qflist)
assert.are.equal('test error', qflist[1].text)
assert.are.equal(1, qflist[1].lnum)
vim.fn.setqflist({}, 'r')
helpers.delete_buffer(bufnr)
end)
it('errors = quickfix clears quickfix on success', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_errors_qf_clear.txt')
vim.bo[bufnr].modified = false
vim.fn.setqflist({ { text = 'old error', lnum = 1 } }, 'r')
assert.are.equal(1, #vim.fn.getqflist())
local provider = {
cmd = { 'true' },
errors = 'quickfix',
error_parser = function()
return {}
end,
}
local ctx = {
bufnr = bufnr,
file = '/tmp/preview_test_errors_qf_clear.txt',
root = '/tmp',
ft = 'text',
}
compiler.compile(bufnr, 'truecmd', provider, ctx)
vim.wait(2000, function()
return compiler._test.active[bufnr] == nil
end, 50)
assert.are.equal(0, #vim.fn.getqflist())
helpers.delete_buffer(bufnr)
end)
end)
describe('stop', function()
it('does nothing when no process is active', function()
assert.has_no.errors(function()