diff --git a/lua/preview/compiler.lua b/lua/preview/compiler.lua index 6e3f5cf..a247f2e 100644 --- a/lua/preview/compiler.lua +++ b/lua/preview/compiler.lua @@ -106,7 +106,8 @@ function M.compile(bufnr, name, provider, ctx) 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) + local output = (result.stdout or '') .. (result.stderr or '') + diagnostic.set(bufnr, name, provider.error_parser, output, ctx) end vim.api.nvim_exec_autocmds('User', { pattern = 'PreviewCompileFailed', diff --git a/lua/preview/diagnostic.lua b/lua/preview/diagnostic.lua index d81ec64..abd4105 100644 --- a/lua/preview/diagnostic.lua +++ b/lua/preview/diagnostic.lua @@ -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 diff --git a/lua/preview/init.lua b/lua/preview/init.lua index f6d0006..d122e61 100644 --- a/lua/preview/init.lua +++ b/lua/preview/init.lua @@ -5,7 +5,7 @@ ---@field cwd? string|fun(ctx: preview.Context): string ---@field env? table ---@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 clean? string[]|fun(ctx: preview.Context): string[] ---@field open? boolean|string[] diff --git a/lua/preview/presets.lua b/lua/preview/presets.lua index 196114b..8b9faab 100644 --- a/lua/preview/presets.lua +++ b/lua/preview/presets.lua @@ -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')) } diff --git a/spec/presets_spec.lua b/spec/presets_spec.lua index 33f3a91..904a4f4 100644 --- a/spec/presets_spec.lua +++ b/spec/presets_spec.lua @@ -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)