preview.nvim/lua/preview/commands.lua
Barrett Ruth c94df7c5d0
fix: lifecycle cleanup and defensive runtime checks (#29)
* 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.
2026-03-04 14:02:16 -05:00

68 lines
1.5 KiB
Lua

local M = {}
local handlers = {
compile = function()
require('preview').compile()
end,
clean = function()
require('preview').clean()
end,
toggle = function()
require('preview').toggle()
end,
open = function()
require('preview').open()
end,
status = function()
local s = require('preview').status()
local parts = {}
if s.compiling then
table.insert(parts, 'compiling with "' .. s.provider .. '"')
else
table.insert(parts, 'idle')
end
if s.watching then
table.insert(parts, 'watching')
end
vim.notify('[preview.nvim]: ' .. table.concat(parts, ', '), vim.log.levels.INFO)
end,
}
---@param args string
local function dispatch(args)
local subcmd = args ~= '' and args or 'toggle'
local handler = handlers[subcmd]
if handler then
handler()
else
vim.notify('[preview.nvim]: unknown subcommand: ' .. subcmd, vim.log.levels.ERROR)
end
end
---@param lead string
---@return string[]
local function complete(lead)
return vim.tbl_filter(function(s)
return s:find(lead, 1, true) == 1
end, vim.tbl_keys(handlers))
end
function M.setup()
vim.api.nvim_create_user_command('Preview', function(opts)
dispatch(opts.args)
end, {
nargs = '?',
complete = function(lead)
return complete(lead)
end,
desc = 'Toggle, compile, clean, open, or check status of document preview',
})
vim.api.nvim_create_autocmd('VimLeavePre', {
callback = function()
require('preview.compiler').stop_all()
end,
})
end
return M