Compare commits
2 commits
build/nix-
...
feat/confi
| Author | SHA1 | Date | |
|---|---|---|---|
| b2e89dcf8b | |||
| 56d110a74e |
6 changed files with 233 additions and 81 deletions
|
|
@ -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)
|
||||
diagnostic.clear(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,8 +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
|
||||
diagnostic.set(bufnr, name, provider.error_parser, result.stderr or '', ctx)
|
||||
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',
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ end
|
|||
|
||||
---@param bufnr integer
|
||||
---@param name string
|
||||
---@param error_parser fun(stderr: string, ctx: preview.Context): preview.Diagnostic[]
|
||||
---@param stderr string
|
||||
---@param error_parser fun(output: string, ctx: preview.Context): preview.Diagnostic[]
|
||||
---@param output string
|
||||
---@param ctx preview.Context
|
||||
function M.set(bufnr, name, error_parser, stderr, ctx)
|
||||
local ok, diagnostics = pcall(error_parser, stderr, ctx)
|
||||
function M.set(bufnr, name, error_parser, output, ctx)
|
||||
local ok, diagnostics = pcall(error_parser, output, ctx)
|
||||
if not ok then
|
||||
log.dbg('error_parser for "%s" failed: %s', name, diagnostics)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
---@field cwd? string|fun(ctx: preview.Context): string
|
||||
---@field env? table<string, string>
|
||||
---@field output? string|fun(ctx: preview.Context): string
|
||||
---@field error_parser? fun(stderr: string, ctx: preview.Context): preview.Diagnostic[]
|
||||
---@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[]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
local M = {}
|
||||
|
||||
---@param stderr string
|
||||
---@param output string
|
||||
---@return preview.Diagnostic[]
|
||||
local function parse_typst(stderr)
|
||||
local function parse_typst(output)
|
||||
local diagnostics = {}
|
||||
for line in stderr:gmatch('[^\r\n]+') do
|
||||
local file, lnum, col, severity, msg = line:match('^(.+):(%d+):(%d+): (%w+): (.+)$')
|
||||
for line in output:gmatch('[^\r\n]+') do
|
||||
local _, lnum, col, severity, msg = line:match('^(.+):(%d+):(%d+): (%w+): (.+)$')
|
||||
if lnum then
|
||||
local sev = vim.diagnostic.severity.ERROR
|
||||
if severity == 'warning' then
|
||||
|
|
@ -16,18 +16,17 @@ local function parse_typst(stderr)
|
|||
col = tonumber(col) - 1,
|
||||
message = msg,
|
||||
severity = sev,
|
||||
source = file,
|
||||
})
|
||||
end
|
||||
end
|
||||
return diagnostics
|
||||
end
|
||||
|
||||
---@param stderr string
|
||||
---@param output string
|
||||
---@return preview.Diagnostic[]
|
||||
local function parse_latexmk(stderr)
|
||||
local function parse_latexmk(output)
|
||||
local diagnostics = {}
|
||||
for line in stderr:gmatch('[^\r\n]+') do
|
||||
for line in output:gmatch('[^\r\n]+') do
|
||||
local _, lnum, msg = line:match('^%.?/?(.+%.tex):(%d+): (.+)$')
|
||||
if lnum then
|
||||
table.insert(diagnostics, {
|
||||
|
|
@ -51,41 +50,45 @@ local function parse_latexmk(stderr)
|
|||
return diagnostics
|
||||
end
|
||||
|
||||
---@param stderr string
|
||||
---@param output string
|
||||
---@return preview.Diagnostic[]
|
||||
local function parse_pandoc(stderr)
|
||||
local function parse_pandoc(output)
|
||||
local diagnostics = {}
|
||||
for line in stderr:gmatch('[^\r\n]+') do
|
||||
local lnum, col, msg = line:match('Error at .+ %(line (%d+), column (%d+)%): (.+)$')
|
||||
local lines = vim.split(output, '\n')
|
||||
local i = 1
|
||||
while i <= #lines do
|
||||
local line = lines[i]
|
||||
local lnum, col, msg = line:match('%(line (%d+), column (%d+)%):%s*(.*)$')
|
||||
if lnum then
|
||||
table.insert(diagnostics, {
|
||||
lnum = tonumber(lnum) - 1,
|
||||
col = tonumber(col) - 1,
|
||||
message = msg,
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
else
|
||||
local ylnum, ycol, ymsg =
|
||||
line:match('YAML parse exception at line (%d+), column (%d+)[,:]%s*(.+)$')
|
||||
if ylnum then
|
||||
table.insert(diagnostics, {
|
||||
lnum = tonumber(ylnum) - 1,
|
||||
col = tonumber(ycol) - 1,
|
||||
message = ymsg,
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
else
|
||||
local errmsg = line:match('^pandoc: (.+)$')
|
||||
if errmsg and not errmsg:match('^Error at') then
|
||||
table.insert(diagnostics, {
|
||||
lnum = 0,
|
||||
col = 0,
|
||||
message = errmsg,
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
if msg == '' then
|
||||
for j = i + 1, math.min(i + 2, #lines) do
|
||||
local next_line = lines[j]:match('^%s*(.+)$')
|
||||
if next_line and not next_line:match('^YAML parse exception') then
|
||||
msg = next_line
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if msg ~= '' then
|
||||
table.insert(diagnostics, {
|
||||
lnum = tonumber(lnum) - 1,
|
||||
col = tonumber(col) - 1,
|
||||
message = msg,
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
end
|
||||
else
|
||||
local errmsg = line:match('^pandoc: (.+)$')
|
||||
if errmsg then
|
||||
table.insert(diagnostics, {
|
||||
lnum = 0,
|
||||
col = 0,
|
||||
message = errmsg,
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return diagnostics
|
||||
end
|
||||
|
|
@ -100,8 +103,8 @@ M.typst = {
|
|||
output = function(ctx)
|
||||
return (ctx.file:gsub('%.typ$', '.pdf'))
|
||||
end,
|
||||
error_parser = function(stderr)
|
||||
return parse_typst(stderr)
|
||||
error_parser = function(output)
|
||||
return parse_typst(output)
|
||||
end,
|
||||
open = true,
|
||||
}
|
||||
|
|
@ -121,8 +124,8 @@ M.latex = {
|
|||
output = function(ctx)
|
||||
return (ctx.file:gsub('%.tex$', '.pdf'))
|
||||
end,
|
||||
error_parser = function(stderr)
|
||||
return parse_latexmk(stderr)
|
||||
error_parser = function(output)
|
||||
return parse_latexmk(output)
|
||||
end,
|
||||
clean = function(ctx)
|
||||
return { 'latexmk', '-c', ctx.file }
|
||||
|
|
@ -141,8 +144,8 @@ M.markdown = {
|
|||
output = function(ctx)
|
||||
return (ctx.file:gsub('%.md$', '.html'))
|
||||
end,
|
||||
error_parser = function(stderr)
|
||||
return parse_pandoc(stderr)
|
||||
error_parser = function(output)
|
||||
return parse_pandoc(output)
|
||||
end,
|
||||
clean = function(ctx)
|
||||
return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) }
|
||||
|
|
@ -171,8 +174,8 @@ M.github = {
|
|||
output = function(ctx)
|
||||
return (ctx.file:gsub('%.md$', '.html'))
|
||||
end,
|
||||
error_parser = function(stderr)
|
||||
return parse_pandoc(stderr)
|
||||
error_parser = function(output)
|
||||
return parse_pandoc(output)
|
||||
end,
|
||||
clean = function(ctx)
|
||||
return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('presets', function()
|
|||
assert.are.equal(22, diagnostics[1].col)
|
||||
assert.are.equal('unexpected token', diagnostics[1].message)
|
||||
assert.are.equal(vim.diagnostic.severity.ERROR, diagnostics[1].severity)
|
||||
assert.are.equal('main.typ', diagnostics[1].source)
|
||||
assert.is_nil(diagnostics[1].source)
|
||||
assert.are.equal(11, diagnostics[2].lnum)
|
||||
assert.are.equal(0, diagnostics[2].col)
|
||||
assert.are.equal('unused variable', diagnostics[2].message)
|
||||
|
|
@ -105,14 +105,14 @@ describe('presets', function()
|
|||
assert.is_true(presets.latex.open)
|
||||
end)
|
||||
|
||||
it('parses file-line-error format from stderr', function()
|
||||
local stderr = table.concat({
|
||||
it('parses file-line-error format from output', function()
|
||||
local output = table.concat({
|
||||
'./document.tex:10: Undefined control sequence.',
|
||||
'l.10 \\badcommand',
|
||||
'Collected error summary (may duplicate other messages):',
|
||||
" pdflatex: Command for 'pdflatex' gave return code 256",
|
||||
}, '\n')
|
||||
local diagnostics = presets.latex.error_parser(stderr, tex_ctx)
|
||||
local diagnostics = presets.latex.error_parser(output, tex_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.is_true(#diagnostics > 0)
|
||||
assert.are.equal(9, diagnostics[1].lnum)
|
||||
|
|
@ -122,12 +122,12 @@ describe('presets', function()
|
|||
end)
|
||||
|
||||
it('parses collected error summary', function()
|
||||
local stderr = table.concat({
|
||||
local output = table.concat({
|
||||
'Latexmk: Errors, so I did not complete making targets',
|
||||
'Collected error summary (may duplicate other messages):',
|
||||
" pdflatex: Command for 'pdflatex' gave return code 256",
|
||||
}, '\n')
|
||||
local diagnostics = presets.latex.error_parser(stderr, tex_ctx)
|
||||
local diagnostics = presets.latex.error_parser(output, tex_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(0, diagnostics[1].lnum)
|
||||
|
|
@ -185,9 +185,24 @@ describe('presets', function()
|
|||
assert.is_true(presets.markdown.open)
|
||||
end)
|
||||
|
||||
it('parses pandoc parse errors from stderr', function()
|
||||
local stderr = 'Error at "source" (line 75, column 1): unexpected end of input'
|
||||
local diagnostics = presets.markdown.error_parser(stderr, md_ctx)
|
||||
it('parses YAML metadata errors with multiline message', function()
|
||||
local output = table.concat({
|
||||
'Error parsing YAML metadata at "/tmp/test.md" (line 1, column 1):',
|
||||
'YAML parse exception at line 1, column 9:',
|
||||
'mapping values are not allowed in this context',
|
||||
}, '\n')
|
||||
local diagnostics = presets.markdown.error_parser(output, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(0, diagnostics[1].lnum)
|
||||
assert.are.equal(0, diagnostics[1].col)
|
||||
assert.are.equal('mapping values are not allowed in this context', diagnostics[1].message)
|
||||
assert.are.equal(vim.diagnostic.severity.ERROR, diagnostics[1].severity)
|
||||
end)
|
||||
|
||||
it('parses Error at format', function()
|
||||
local output = 'Error at "source" (line 75, column 1): unexpected end of input'
|
||||
local diagnostics = presets.markdown.error_parser(output, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(74, diagnostics[1].lnum)
|
||||
|
|
@ -196,20 +211,9 @@ describe('presets', function()
|
|||
assert.are.equal(vim.diagnostic.severity.ERROR, diagnostics[1].severity)
|
||||
end)
|
||||
|
||||
it('parses YAML parse exceptions from stderr', function()
|
||||
local stderr =
|
||||
'YAML parse exception at line 3, column 2, while scanning a block scalar: did not find expected comment or line break'
|
||||
local diagnostics = presets.markdown.error_parser(stderr, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(2, diagnostics[1].lnum)
|
||||
assert.are.equal(1, diagnostics[1].col)
|
||||
assert.is_string(diagnostics[1].message)
|
||||
end)
|
||||
|
||||
it('parses generic pandoc errors from stderr', function()
|
||||
local stderr = 'pandoc: Could not find data file templates/default.html5'
|
||||
local diagnostics = presets.markdown.error_parser(stderr, md_ctx)
|
||||
it('parses generic pandoc errors', function()
|
||||
local output = 'pandoc: Could not find data file templates/default.html5'
|
||||
local diagnostics = presets.markdown.error_parser(output, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(0, diagnostics[1].lnum)
|
||||
|
|
@ -217,7 +221,7 @@ describe('presets', function()
|
|||
assert.are.equal('Could not find data file templates/default.html5', diagnostics[1].message)
|
||||
end)
|
||||
|
||||
it('returns empty table for clean stderr', function()
|
||||
it('returns empty table for clean output', function()
|
||||
local diagnostics = presets.markdown.error_parser('', md_ctx)
|
||||
assert.are.same({}, diagnostics)
|
||||
end)
|
||||
|
|
@ -284,9 +288,23 @@ describe('presets', function()
|
|||
assert.is_true(presets.github.open)
|
||||
end)
|
||||
|
||||
it('parses pandoc parse errors from stderr', function()
|
||||
local stderr = 'Error at "document.md" (line 12, column 5): unexpected "}" expecting letter'
|
||||
local diagnostics = presets.github.error_parser(stderr, md_ctx)
|
||||
it('parses YAML metadata errors with multiline message', function()
|
||||
local output = table.concat({
|
||||
'Error parsing YAML metadata at "/tmp/test.md" (line 1, column 1):',
|
||||
'YAML parse exception at line 1, column 9:',
|
||||
'mapping values are not allowed in this context',
|
||||
}, '\n')
|
||||
local diagnostics = presets.github.error_parser(output, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(0, diagnostics[1].lnum)
|
||||
assert.are.equal(0, diagnostics[1].col)
|
||||
assert.are.equal('mapping values are not allowed in this context', diagnostics[1].message)
|
||||
end)
|
||||
|
||||
it('parses Error at format', function()
|
||||
local output = 'Error at "document.md" (line 12, column 5): unexpected "}" expecting letter'
|
||||
local diagnostics = presets.github.error_parser(output, md_ctx)
|
||||
assert.is_table(diagnostics)
|
||||
assert.are.equal(1, #diagnostics)
|
||||
assert.are.equal(11, diagnostics[1].lnum)
|
||||
|
|
@ -295,7 +313,7 @@ describe('presets', function()
|
|||
assert.are.equal(vim.diagnostic.severity.ERROR, diagnostics[1].severity)
|
||||
end)
|
||||
|
||||
it('returns empty table for clean stderr', function()
|
||||
it('returns empty table for clean output', function()
|
||||
local diagnostics = presets.github.error_parser('', md_ctx)
|
||||
assert.are.same({}, diagnostics)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue