* fix(commands): register VimLeavePre to call stop_all
Problem: spawned compiler processes and watching autocmds were never
cleaned up when Neovim exited, leaving orphaned processes running.
Solution: register a VimLeavePre autocmd in commands setup that calls
compiler.stop_all(), which kills active processes, unwatches all
buffers, and stops the reload server.
* fix(compiler): replace BufWipeout with BufUnload
Problem: cleanup autocmds used BufWipeout, which only fires for
:bwipeout. The common :bdelete path (used by most buffer managers
and nvim_buf_delete) fires BufUnload but not BufWipeout, so processes
and watches leaked on normal buffer deletion.
Solution: switch all three cleanup autocmds from BufWipeout to
BufUnload, which fires for both :bdelete and :bwipeout.
* fix(init): guard against unnamed buffer in public API
Problem: calling compile/toggle/clean/open on an unsaved scratch
buffer passed an empty string as ctx.file, producing nonsensical
output paths like ".pdf" and silently passing empty strings to
compiler binaries.
Solution: add an early return with a WARN notification in compile,
toggle, clean, and open when the buffer has no file name.
* fix(compiler): add fs_stat check to one-shot open path
Problem: the long-running process path already guarded opens with
vim.uv.fs_stat(), but the one-shot compile path and M.open() did not.
Compilation can exit 0 and produce no output, and output files can be
externally deleted between compile and open.
Solution: add the same fs_stat guard to the one-shot open branch and
to M.open() before attempting to launch the viewer.
* fix(compiler): check executable before spawning process
Problem: if a configured binary was missing or not in PATH, vim.system
would fail silently or with a cryptic OS error. The user had no
actionable feedback without running :checkhealth.
Solution: check vim.fn.executable() at the start of M.compile() and
notify with an ERROR-level message pointing to :checkhealth preview
if the binary is not found.
* fix(compiler): reformat one-shot open condition for line length
Problem: the added fs_stat condition exceeded stylua's line length
limit on the one-shot open guard.
Solution: split the boolean condition across multiple lines to match
the project's stylua formatting rules.
* refactor: rename build to compile and watch to toggle in public API
Problem: the code used build/watch while the help file already
documented compile/toggle, creating a confusing mismatch.
Solution: rename M.build() to M.compile() and M.watch() to M.toggle()
in init.lua, update handler keys in commands.lua, and update the test
file to match.
* refactor(commands): make toggle the default subcommand
Problem: bare :Preview ran a one-shot compile, but users reaching for a
"preview" plugin expect it to start previewing (i.e. watch mode).
Solution: change the fallback subcommand from compile to toggle so
:Preview starts/stops auto-compile on save.
* refactor(commands): remove stop subcommand
Problem: :Preview stop had a subtle distinction from toggle-off (kill
process but keep autocmd) that nobody reaches for deliberately from
the command line.
Solution: remove stop from the command dispatch table. The Lua API
require('preview').stop() remains as a programmatic escape hatch.
* docs: update help file for new command surface and document reload
Problem: the help file listed compile as the default subcommand, still
included the stop subcommand, omitted the reload provider field, and
had a misleading claim about shipping with zero defaults.
Solution: make toggle the default in the commands section, remove stop
from subcommands, add reload to provider fields, fix the introduction
text, reorder API entries to match new primacy, and add an output path
override example addressing #26/#27.
* feat(reload): add SSE live-reload server module
Problem: HTML output from pandoc has no live-reload; the browser must
be refreshed manually after each compile.
Solution: add lua/preview/reload.lua — a minimal SSE-only TCP server.
start() binds 127.0.0.1:5554 and keeps EventSource connections alive;
broadcast() pushes a reload event to all clients; inject() appends an
EventSource script before </body> (or at EOF) on every compile so
pandoc overwrites do not lose the tag.
* refactor(presets): add reload field, remove synctex field
Problem: the synctex field only handled PDF forward search and left
HTML live-preview and typst watch mode unsupported.
Solution: add reload = function(ctx) returning { 'typst', 'watch',
ctx.file } to typst (long-running watch mode), reload = true to
markdown and github (SSE push after each pandoc compile), and remove
synctex = true from latex (the -synctex=1 arg in latex.args remains
for .synctex.gz generation).
* refactor(init): replace synctex field and validation with reload
Problem: ProviderConfig still declared synctex and validated it, but
the field is being dropped in favour of the general-purpose reload.
Solution: replace the synctex annotation and vim.validate call with the
reload field, accepting boolean | string[] | function.
* feat(compiler): support long-running watch processes and SSE reload
Problem: compile() only supports one-shot invocations, requiring a
BufWritePost autocmd for watch mode and leaving HTML without live-
reload.
Solution: resolve_reload_cmd() maps provider.reload (function or table)
to a command; when present, compile() spawns it as a long-running
process instead of building a one-shot cmd from provider.cmd + args.
toggle() detects long-running providers and toggles the process
directly instead of registering a BufWritePost autocmd. When
reload = true and output is .html, the SSE server is invoked after
each successful compile. status() reports is_reload processes as
watching, not compiling. stop_all() also stops the SSE server.
* fix(compiler): format is_longrunning and annotate is_reload field
Problem: stylua required is_longrunning to be on one line; lua-ls
warned about undefined field is_reload on preview.Process.
Solution: inline the boolean expression and add is_reload? to the
preview.Process annotation.
* refactor: rename compile/toggle commands to build/watch
Problem: `compile` and `toggle` are accurate but unintuitive — `compile`
sounds academic and `toggle` says nothing about what it toggles.
Solution: rename the public API and `:Preview` subcommands to `build`
(one-shot) and `watch` (live preview). Internal compiler functions are
unchanged. No aliases for old names — clean break.
Problem: :Preview toggle gave no feedback, leaving the user to guess
whether watching was enabled or disabled.
Solution: emit vim.notify messages when toggling on ("watching with
\"<provider>\"") and off ("watching stopped"). Also normalize the
[preview.nvim] prefix in commands.lua to include the colon.
Problem: after closing a viewer, there was no way to re-open the last
compiled output without recompiling.
Solution: track the most recent output file per buffer in a `last_output`
table that persists after compilation finishes. Add `compiler.open()`,
`M.open()`, and wire it into the command dispatch.
Problem: :Preview watch only registered a BufWritePost autocmd without
compiling immediately, required boilerplate to open output files after
first compilation, and was misleadingly named.
Solution: Rename watch → toggle throughout. M.toggle now compiles
immediately on activation. Add an open field to ProviderConfig: true
calls vim.ui.open(), a string[] runs the command with the output path
appended, tracked per-buffer so the file opens only once. All presets
default to { 'xdg-open' }. Health check validates opener binaries.
Guard the async compile callback against invalid buffer ids.