feat: compile notifications and long-running provider feedback (#55)
* feat(compiler): add compile start/complete notifications Problem: No user-facing feedback when compilation starts or finishes. Long-running compilers like pandoc with `--embed-resources` leave the user staring at nothing for 15+ seconds. Solution: Notify "compiling..." at compile start and "compilation complete" on success. The initial `toggle` call uses a combined "compiling with <name>..." message to avoid stacking two notifications. * refactor(presets): use `--katex` instead of `--embed-resources --mathml` Problem: `--embed-resources` with `--mathml` caused pandoc to inline all assets at compile time, adding ~15s per save for KaTeX-heavy documents. Solution: Default to `--katex`, which inserts a CDN `<script>` tag and defers rendering to the browser. Users can opt into `--embed-resources` or `--mathml` via `extra_args`. * docs(presets): rewrite math rendering section for `--katex` default * refactor(compiler): simplify notification flow, add failure notify Problem: `compile()` used an `opts.silent` escape hatch so `toggle()` could suppress duplicate notifications. Compilation failures had no user-facing notification. Solution: Remove `opts.silent` — `compile()` unconditionally notifies on start, success, and failure. `toggle()` no longer emits its own message. * feat(compiler): add per-recompile notifications for long-running providers Problem: long-running providers like `typst watch` had no per-recompile feedback — the process stays alive and individual success/failure was never reported. Solution: add a persistent `output_watcher` fs_event that fires "compilation complete" on every output mtime bump, and track `has_errors` on `BufState` so stderr diagnostics trigger a one-shot "compilation failed" notification. `diagnostic.set()` now returns the diagnostic count to support this flow. * ci: format * chore: remove testing files
This commit is contained in:
parent
aeea1bd8fa
commit
39406c559c
7 changed files with 217 additions and 44 deletions
|
|
@ -12,6 +12,8 @@ local log = require('preview.log')
|
|||
---@field viewer? table
|
||||
---@field viewer_open? boolean
|
||||
---@field open_watcher? uv.uv_fs_event_t
|
||||
---@field output_watcher? uv.uv_fs_event_t
|
||||
---@field has_errors? boolean
|
||||
---@field debounce? uv.uv_timer_t
|
||||
---@field bwp_autocmd? integer
|
||||
---@field unload_autocmd? integer
|
||||
|
|
@ -41,6 +43,17 @@ local function stop_open_watcher(bufnr)
|
|||
s.open_watcher = nil
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
local function stop_output_watcher(bufnr)
|
||||
local s = state[bufnr]
|
||||
if not (s and s.output_watcher) then
|
||||
return
|
||||
end
|
||||
s.output_watcher:stop()
|
||||
s.output_watcher:close()
|
||||
s.output_watcher = nil
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
local function close_viewer(bufnr)
|
||||
local s = state[bufnr]
|
||||
|
|
@ -56,16 +69,17 @@ end
|
|||
---@param provider preview.ProviderConfig
|
||||
---@param ctx preview.Context
|
||||
---@param output string
|
||||
---@return integer
|
||||
local function handle_errors(bufnr, name, provider, ctx, output)
|
||||
local errors_mode = provider.errors
|
||||
if errors_mode == nil then
|
||||
errors_mode = 'diagnostic'
|
||||
end
|
||||
if not (provider.error_parser and errors_mode) then
|
||||
return
|
||||
return 0
|
||||
end
|
||||
if errors_mode == 'diagnostic' then
|
||||
diagnostic.set(bufnr, name, provider.error_parser, output, ctx)
|
||||
return diagnostic.set(bufnr, name, provider.error_parser, output, ctx)
|
||||
elseif errors_mode == 'quickfix' then
|
||||
local ok, diags = pcall(provider.error_parser, output, ctx)
|
||||
if ok and diags and #diags > 0 then
|
||||
|
|
@ -83,8 +97,10 @@ local function handle_errors(bufnr, name, provider, ctx, output)
|
|||
local win = vim.fn.win_getid()
|
||||
vim.cmd.cwindow()
|
||||
vim.fn.win_gotoid(win)
|
||||
return #diags
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
|
|
@ -169,6 +185,7 @@ local function stop_watching(bufnr, s)
|
|||
s.watching = false
|
||||
M.stop(bufnr)
|
||||
stop_open_watcher(bufnr)
|
||||
stop_output_watcher(bufnr)
|
||||
close_viewer(bufnr)
|
||||
s.viewer_open = nil
|
||||
if s.bwp_autocmd then
|
||||
|
|
@ -250,7 +267,11 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
return
|
||||
end
|
||||
stderr_acc[#stderr_acc + 1] = data
|
||||
handle_errors(bufnr, name, provider, ctx, table.concat(stderr_acc))
|
||||
local count = handle_errors(bufnr, name, provider, ctx, table.concat(stderr_acc))
|
||||
if count > 0 and not s.has_errors then
|
||||
s.has_errors = true
|
||||
vim.notify('[preview.nvim]: compilation failed', vim.log.levels.ERROR)
|
||||
end
|
||||
end),
|
||||
},
|
||||
vim.schedule_wrap(function(result)
|
||||
|
|
@ -263,6 +284,7 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
end
|
||||
if result.code ~= 0 then
|
||||
log.dbg('long-running process failed for buffer %d (exit code %d)', bufnr, result.code)
|
||||
vim.notify('[preview.nvim]: compilation failed', vim.log.levels.ERROR)
|
||||
handle_errors(bufnr, name, provider, ctx, (result.stdout or '') .. (result.stderr or ''))
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileFailed',
|
||||
|
|
@ -325,10 +347,54 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
end
|
||||
end
|
||||
|
||||
if output_file ~= '' then
|
||||
local out_dir = vim.fn.fnamemodify(output_file, ':h')
|
||||
local out_name = vim.fn.fnamemodify(output_file, ':t')
|
||||
stop_output_watcher(bufnr)
|
||||
local ow = vim.uv.new_fs_event()
|
||||
if ow then
|
||||
s.output_watcher = ow
|
||||
local last_mtime = 0
|
||||
local stat = vim.uv.fs_stat(output_file)
|
||||
if stat then
|
||||
last_mtime = stat.mtime.sec
|
||||
end
|
||||
ow:start(
|
||||
out_dir,
|
||||
{},
|
||||
vim.schedule_wrap(function(err, filename, _events)
|
||||
if err or vim.fn.fnamemodify(filename or '', ':t') ~= out_name then
|
||||
return
|
||||
end
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
stop_output_watcher(bufnr)
|
||||
return
|
||||
end
|
||||
local new_stat = vim.uv.fs_stat(output_file)
|
||||
if not (new_stat and new_stat.mtime.sec > last_mtime) then
|
||||
return
|
||||
end
|
||||
last_mtime = new_stat.mtime.sec
|
||||
log.dbg('output updated for buffer %d', bufnr)
|
||||
vim.notify('[preview.nvim]: compilation complete', vim.log.levels.INFO)
|
||||
stderr_acc = {}
|
||||
s.has_errors = false
|
||||
clear_errors(bufnr, provider)
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileSuccess',
|
||||
data = { bufnr = bufnr, provider = name, output = output_file },
|
||||
})
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
s.process = obj
|
||||
s.provider = name
|
||||
s.is_reload = true
|
||||
s.has_errors = false
|
||||
|
||||
vim.notify('[preview.nvim]: compiling...', vim.log.levels.INFO)
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileStarted',
|
||||
data = { bufnr = bufnr, provider = name },
|
||||
|
|
@ -360,6 +426,7 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
end
|
||||
if result.code == 0 then
|
||||
log.dbg('compilation succeeded for buffer %d', bufnr)
|
||||
vim.notify('[preview.nvim]: compilation complete', vim.log.levels.INFO)
|
||||
clear_errors(bufnr, provider)
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileSuccess',
|
||||
|
|
@ -385,6 +452,7 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
end
|
||||
else
|
||||
log.dbg('compilation failed for buffer %d (exit code %d)', bufnr, result.code)
|
||||
vim.notify('[preview.nvim]: compilation failed', vim.log.levels.ERROR)
|
||||
handle_errors(bufnr, name, provider, ctx, (result.stdout or '') .. (result.stderr or ''))
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileFailed',
|
||||
|
|
@ -403,6 +471,7 @@ function M.compile(bufnr, name, provider, ctx, opts)
|
|||
s.provider = name
|
||||
s.is_reload = false
|
||||
|
||||
vim.notify('[preview.nvim]: compiling...', vim.log.levels.INFO)
|
||||
vim.api.nvim_exec_autocmds('User', {
|
||||
pattern = 'PreviewCompileStarted',
|
||||
data = { bufnr = bufnr, provider = name },
|
||||
|
|
@ -415,6 +484,7 @@ function M.stop(bufnr)
|
|||
if not s then
|
||||
return
|
||||
end
|
||||
stop_output_watcher(bufnr)
|
||||
local obj = s.process
|
||||
if not obj then
|
||||
return
|
||||
|
|
@ -480,6 +550,7 @@ function M.toggle(bufnr, name, provider, ctx_builder)
|
|||
callback = function()
|
||||
M.stop(bufnr)
|
||||
stop_open_watcher(bufnr)
|
||||
stop_output_watcher(bufnr)
|
||||
if not provider.detach then
|
||||
close_viewer(bufnr)
|
||||
end
|
||||
|
|
@ -512,7 +583,6 @@ function M.toggle(bufnr, name, provider, ctx_builder)
|
|||
log.dbg('watching buffer %d with provider "%s"', bufnr, name)
|
||||
end
|
||||
|
||||
vim.notify('[preview.nvim]: watching with "' .. name .. '"', vim.log.levels.INFO)
|
||||
M.compile(bufnr, name, provider, ctx_builder(bufnr))
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue