From af8bb491222727426823d019478647bf7afd9aad Mon Sep 17 00:00:00 2001
From: Barrett Ruth
Date: Tue, 3 Mar 2026 15:10:06 -0500
Subject: [PATCH 01/34] refactor(compiler): resolve output before args
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 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 02/34] 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 03/34] 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 04/34] 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