From 7ed4b61c988dc1f400bebe634e3b212936f5c96d Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:03:48 -0500 Subject: [PATCH 01/35] refactor(commands): derive completion from dispatch table (#15) --- lua/preview/commands.lua | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lua/preview/commands.lua b/lua/preview/commands.lua index f82c726..2c52c5a 100644 --- a/lua/preview/commands.lua +++ b/lua/preview/commands.lua @@ -1,22 +1,22 @@ local M = {} -local subcommands = { 'compile', 'stop', 'clean', 'toggle', 'open', 'status' } - ----@param args string -local function dispatch(args) - local subcmd = args ~= '' and args or 'compile' - - if subcmd == 'compile' then +local handlers = { + compile = function() require('preview').compile() - elseif subcmd == 'stop' then + end, + stop = function() require('preview').stop() - elseif subcmd == 'clean' then + end, + clean = function() require('preview').clean() - elseif subcmd == 'toggle' then + end, + toggle = function() require('preview').toggle() - elseif subcmd == 'open' then + end, + open = function() require('preview').open() - elseif subcmd == 'status' then + end, + status = function() local s = require('preview').status() local parts = {} if s.compiling then @@ -28,6 +28,15 @@ local function dispatch(args) 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 'compile' + local handler = handlers[subcmd] + if handler then + handler() else vim.notify('[preview.nvim]: unknown subcommand: ' .. subcmd, vim.log.levels.ERROR) end @@ -38,7 +47,7 @@ end local function complete(lead) return vim.tbl_filter(function(s) return s:find(lead, 1, true) == 1 - end, subcommands) + end, vim.tbl_keys(handlers)) end function M.setup() From 4c22f84b31ebfee1b8fd59dbb254917582f3985e Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:04:03 -0500 Subject: [PATCH 02/35] feat(init): validate provider config eagerly in setup (#16) --- lua/preview/init.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lua/preview/init.lua b/lua/preview/init.lua index 4a44a33..f4f2831 100644 --- a/lua/preview/init.lua +++ b/lua/preview/init.lua @@ -85,6 +85,20 @@ function M.setup(opts) end end + for ft, provider in pairs(providers) do + local prefix = 'providers.' .. ft + vim.validate(prefix .. '.cmd', provider.cmd, 'table') + vim.validate(prefix .. '.cmd[1]', provider.cmd[1], 'string') + vim.validate(prefix .. '.args', provider.args, { 'table', 'function' }, true) + vim.validate(prefix .. '.cwd', provider.cwd, { 'string', 'function' }, true) + vim.validate(prefix .. '.output', provider.output, { 'string', 'function' }, true) + vim.validate(prefix .. '.error_parser', provider.error_parser, 'function', true) + vim.validate(prefix .. '.errors', provider.errors, function(x) + return x == nil or x == false or x == 'diagnostic' or x == 'quickfix' + end, 'false, "diagnostic", or "quickfix"') + vim.validate(prefix .. '.open', provider.open, { 'boolean', 'table' }, true) + end + config = vim.tbl_deep_extend('force', default_config, { debug = debug, providers = providers, From 99263dec9f33712eacf001b703acff5166a9f6a6 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:12:14 -0500 Subject: [PATCH 03/35] refactor(compiler): resolve output before args (#17) Problem: presets that need the output path in their args function (markdown, github) had to recompute it inline, duplicating the same gsub expression already in the output field. Solution: resolve output_file first in M.compile, then extend ctx with output = output_file into a resolved_ctx before evaluating args and cwd. Presets can now reference ctx.output directly. Add output? to the preview.Context type annotation. --- lua/preview/compiler.lua | 16 +++++++++------- lua/preview/init.lua | 1 + lua/preview/presets.lua | 6 ++---- spec/presets_spec.lua | 2 ++ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lua/preview/compiler.lua b/lua/preview/compiler.lua index 0643ccd..eca9f4d 100644 --- a/lua/preview/compiler.lua +++ b/lua/preview/compiler.lua @@ -53,19 +53,21 @@ function M.compile(bufnr, name, provider, ctx) M.stop(bufnr) end + local output_file = '' + if provider.output then + output_file = eval_string(provider.output, ctx) + end + + local resolved_ctx = vim.tbl_extend('force', ctx, { output = output_file }) + local cmd = vim.list_extend({}, provider.cmd) if provider.args then - vim.list_extend(cmd, eval_list(provider.args, ctx)) + vim.list_extend(cmd, eval_list(provider.args, resolved_ctx)) end local cwd = ctx.root if provider.cwd then - cwd = eval_string(provider.cwd, ctx) - end - - local output_file = '' - if provider.output then - output_file = eval_string(provider.output, ctx) + cwd = eval_string(provider.cwd, resolved_ctx) end if output_file ~= '' then diff --git a/lua/preview/init.lua b/lua/preview/init.lua index f4f2831..2bee03a 100644 --- a/lua/preview/init.lua +++ b/lua/preview/init.lua @@ -19,6 +19,7 @@ ---@field file string ---@field root string ---@field ft string +---@field output? string ---@class preview.Diagnostic ---@field lnum integer diff --git a/lua/preview/presets.lua b/lua/preview/presets.lua index 8b9faab..c1de0df 100644 --- a/lua/preview/presets.lua +++ b/lua/preview/presets.lua @@ -138,8 +138,7 @@ M.markdown = { ft = 'markdown', cmd = { 'pandoc' }, args = function(ctx) - local output = ctx.file:gsub('%.md$', '.html') - return { ctx.file, '-s', '--embed-resources', '-o', output } + return { ctx.file, '-s', '--embed-resources', '-o', ctx.output } end, output = function(ctx) return (ctx.file:gsub('%.md$', '.html')) @@ -158,7 +157,6 @@ M.github = { ft = 'markdown', cmd = { 'pandoc' }, args = function(ctx) - local output = ctx.file:gsub('%.md$', '.html') return { '-f', 'gfm', @@ -168,7 +166,7 @@ M.github = { '--css', 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/dist/gfm.css', '-o', - output, + ctx.output, } end, output = function(ctx) diff --git a/spec/presets_spec.lua b/spec/presets_spec.lua index 904a4f4..6eaa613 100644 --- a/spec/presets_spec.lua +++ b/spec/presets_spec.lua @@ -150,6 +150,7 @@ describe('presets', function() file = '/tmp/document.md', root = '/tmp', ft = 'markdown', + output = '/tmp/document.html', } it('has ft', function() @@ -233,6 +234,7 @@ describe('presets', function() file = '/tmp/document.md', root = '/tmp', ft = 'markdown', + output = '/tmp/document.html', } it('has ft', function() From bce3cec0e66654eb7b13ad46c5dbe8f9209e72de Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:12:28 -0500 Subject: [PATCH 04/35] docs: update help file for recent additions (#18) --- doc/preview.nvim.txt | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/doc/preview.nvim.txt b/doc/preview.nvim.txt index 0b44a91..c8bc708 100644 --- a/doc/preview.nvim.txt +++ b/doc/preview.nvim.txt @@ -74,9 +74,16 @@ Provider fields:~ `output` string|function Output file path. If a function, receives a |preview.Context|. - `error_parser` function Receives (stderr, |preview.Context|) + `error_parser` function Receives (output, |preview.Context|) and returns vim.Diagnostic[]. + `errors` false|'diagnostic'|'quickfix' + How parse errors are reported. + `false` suppresses error handling. + `'quickfix'` populates the quickfix + list and opens it. Default: + `'diagnostic'`. + `clean` string[]|function Command to remove build artifacts. If a function, receives a |preview.Context|. @@ -85,7 +92,6 @@ Provider fields:~ successful compilation. `true` uses |vim.ui.open()|. A string[] is run as a command with the output path appended. - Presets default to `{ 'xdg-open' }`. *preview.Context* Context fields:~ @@ -94,6 +100,8 @@ Context fields:~ `file` string Absolute file path. `root` string Project root (git root or file directory). `ft` string Filetype. + `output` string? Resolved output file path (set after `output` + is evaluated, available to `args` functions). Example enabling presets:~ >lua @@ -156,7 +164,8 @@ COMMANDS *preview.nvim-commands* `stop` Kill active compilation for the current buffer. `clean` Run the provider's clean command. `toggle` Toggle auto-compile on save for the current buffer. - `status` Echo compilation status (idle, compiling, toggled). + `open` Open the last compiled output without recompiling. + `status` Echo compilation status (idle, compiling, watching). ============================================================================== API *preview.nvim-api* @@ -175,9 +184,16 @@ preview.toggle({bufnr?}) *preview.toggle()* immediately compiled and automatically recompiled on each save (`BufWritePost`). Call again to stop. +preview.open({bufnr?}) *preview.open()* + Open the last compiled output for the buffer without recompiling. + preview.status({bufnr?}) *preview.status()* Returns a |preview.Status| table. +preview.statusline({bufnr?}) *preview.statusline()* + Returns a short status string for statusline integration: + `'compiling'`, `'watching'`, or `''` (idle). + *preview.Status* Status fields:~ From 62961c854168cec9ae30e52c3e1b2e914248f1f2 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:41:47 -0500 Subject: [PATCH 05/35] feat: unified reload field for live-preview (SSE + long-running watch) (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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
hello