Problem: all three built-in error parsers were broken against real compiler output. Typst set source to the relative file path, overriding the provider name. LaTeX errors go to stdout but the parser only received stderr. Pandoc's pattern matched "Error at" but not the real "Error parsing YAML metadata at" format, and single-line parsing missed multiline messages. Solution: pass combined stdout+stderr to error_parser so LaTeX stdout errors are visible. Remove source = file from the Typst parser so diagnostic.lua defaults it to the provider name. Rewrite the Pandoc parser with line-based lookahead: match (line N, column N) regardless of prefix text, skip YAML parse exception lines when looking ahead for the human-readable message. Rename stderr param to output throughout diagnostic.lua, presets.lua, and init.lua annotations.
212 lines
5.5 KiB
Lua
212 lines
5.5 KiB
Lua
---@class preview.ProviderConfig
|
|
---@field ft? string
|
|
---@field cmd string[]
|
|
---@field args? string[]|fun(ctx: preview.Context): string[]
|
|
---@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(output: string, ctx: preview.Context): preview.Diagnostic[]
|
|
---@field clean? string[]|fun(ctx: preview.Context): string[]
|
|
---@field open? boolean|string[]
|
|
|
|
---@class preview.Config
|
|
---@field debug boolean|string
|
|
---@field providers table<string, preview.ProviderConfig>
|
|
|
|
---@class preview.Context
|
|
---@field bufnr integer
|
|
---@field file string
|
|
---@field root string
|
|
---@field ft string
|
|
|
|
---@class preview.Diagnostic
|
|
---@field lnum integer
|
|
---@field col integer
|
|
---@field message string
|
|
---@field severity? integer
|
|
---@field end_lnum? integer
|
|
---@field end_col? integer
|
|
---@field source? string
|
|
|
|
---@class preview.Process
|
|
---@field obj table
|
|
---@field provider string
|
|
---@field output_file string
|
|
|
|
---@class preview
|
|
---@field setup fun(opts?: table)
|
|
---@field compile fun(bufnr?: integer)
|
|
---@field stop fun(bufnr?: integer)
|
|
---@field clean fun(bufnr?: integer)
|
|
---@field toggle fun(bufnr?: integer)
|
|
---@field open fun(bufnr?: integer)
|
|
---@field status fun(bufnr?: integer): preview.Status
|
|
---@field statusline fun(bufnr?: integer): string
|
|
---@field get_config fun(): preview.Config
|
|
local M = {}
|
|
|
|
local compiler = require('preview.compiler')
|
|
local log = require('preview.log')
|
|
|
|
---@type preview.Config
|
|
local default_config = {
|
|
debug = false,
|
|
providers = {},
|
|
}
|
|
|
|
---@type preview.Config
|
|
local config = vim.deepcopy(default_config)
|
|
|
|
---@param opts? table
|
|
function M.setup(opts)
|
|
opts = opts or {}
|
|
vim.validate('preview.setup opts', opts, 'table')
|
|
|
|
local presets = require('preview.presets')
|
|
local providers = {}
|
|
local debug = false
|
|
|
|
for k, v in pairs(opts) do
|
|
if k == 'debug' then
|
|
vim.validate('preview.setup opts.debug', v, { 'boolean', 'string' })
|
|
debug = v
|
|
elseif type(k) ~= 'number' then
|
|
local preset = presets[k]
|
|
if preset then
|
|
if v == true then
|
|
providers[preset.ft] = preset
|
|
elseif type(v) == 'table' then
|
|
providers[preset.ft] = vim.tbl_deep_extend('force', preset, v)
|
|
end
|
|
elseif type(v) == 'table' then
|
|
providers[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
config = vim.tbl_deep_extend('force', default_config, {
|
|
debug = debug,
|
|
providers = providers,
|
|
})
|
|
|
|
log.set_enabled(config.debug)
|
|
log.dbg('initialized with %d providers', vim.tbl_count(config.providers))
|
|
end
|
|
|
|
---@return preview.Config
|
|
function M.get_config()
|
|
return config
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
---@return string?
|
|
function M.resolve_provider(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local ft = vim.bo[bufnr].filetype
|
|
if not config.providers[ft] then
|
|
log.dbg('no provider configured for filetype: %s', ft)
|
|
return nil
|
|
end
|
|
return ft
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
---@return preview.Context
|
|
function M.build_context(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local file = vim.api.nvim_buf_get_name(bufnr)
|
|
local root = vim.fs.root(bufnr, { '.git' }) or vim.fn.fnamemodify(file, ':h')
|
|
return {
|
|
bufnr = bufnr,
|
|
file = file,
|
|
root = root,
|
|
ft = vim.bo[bufnr].filetype,
|
|
}
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
function M.compile(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local name = M.resolve_provider(bufnr)
|
|
if not name then
|
|
vim.notify('[preview.nvim] no provider configured for this filetype', vim.log.levels.WARN)
|
|
return
|
|
end
|
|
local ctx = M.build_context(bufnr)
|
|
local provider = config.providers[name]
|
|
compiler.compile(bufnr, name, provider, ctx)
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
function M.stop(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
compiler.stop(bufnr)
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
function M.clean(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local name = M.resolve_provider(bufnr)
|
|
if not name then
|
|
vim.notify('[preview.nvim] no provider configured for this filetype', vim.log.levels.WARN)
|
|
return
|
|
end
|
|
local ctx = M.build_context(bufnr)
|
|
local provider = config.providers[name]
|
|
compiler.clean(bufnr, name, provider, ctx)
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
function M.toggle(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local name = M.resolve_provider(bufnr)
|
|
if not name then
|
|
vim.notify('[preview.nvim] no provider configured for this filetype', vim.log.levels.WARN)
|
|
return
|
|
end
|
|
local provider = config.providers[name]
|
|
compiler.toggle(bufnr, name, provider, M.build_context)
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
function M.open(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
if not compiler.open(bufnr) then
|
|
vim.notify('[preview.nvim] no output file available for this buffer', vim.log.levels.WARN)
|
|
end
|
|
end
|
|
|
|
---@class preview.Status
|
|
---@field compiling boolean
|
|
---@field watching boolean
|
|
---@field provider? string
|
|
---@field output_file? string
|
|
|
|
---@param bufnr? integer
|
|
---@return preview.Status
|
|
function M.status(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
return compiler.status(bufnr)
|
|
end
|
|
|
|
---@param bufnr? integer
|
|
---@return string
|
|
function M.statusline(bufnr)
|
|
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
|
local s = compiler.status(bufnr)
|
|
if s.compiling then
|
|
return 'compiling'
|
|
elseif s.watching then
|
|
return 'watching'
|
|
end
|
|
return ''
|
|
end
|
|
|
|
M._test = {
|
|
---@diagnostic disable-next-line: assign-type-mismatch
|
|
reset = function()
|
|
config = vim.deepcopy(default_config)
|
|
end,
|
|
}
|
|
|
|
return M
|