Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
50580ecde5
refactor: remove PreviewWatch* events and clean up docs
Problem: PreviewWatchStarted/PreviewWatchStopped were redundant with
the status() API, and the doc had a wrong author, stale INSTALLATION
format, and "watch mode" language left over from the watch → toggle
rename.

Solution: Remove the events and their tests. Fix the doc author,
rename INSTALLATION → SETUP to match sibling plugins, replace "watch
mode" with "auto-compile" throughout, and drop the events from EVENTS.
2026-03-03 00:44:46 -05:00
6e48c6716b
ci: remove superfluous things 2026-03-03 00:26:37 -05:00
2ec1b2d206
fix(presets): parenthesize gsub output to suppress redundant-return-value 2026-03-03 00:24:46 -05:00
26c15a701e
ci: format 2026-03-03 00:23:35 -05:00
859f04e010
refactor(config): replace array preset syntax with preset_name = true
Problem: setup() mixed array entries (preset names) and hash entries
(custom providers keyed by filetype), requiring verbose
vim.tbl_deep_extend boilerplate to override presets.

Solution: unify under a single key=value model. Keys are preset names
or filetypes; true registers the preset as-is, a table deep-merges
with the matching preset (or registers a custom provider if no preset
matches), and false is a no-op. Array entries are dropped. Also adds
-f gfm to presets.github args so pandoc parses input as GFM.
2026-03-03 00:19:48 -05:00
8 changed files with 81 additions and 138 deletions

View file

@ -55,24 +55,19 @@ require('preview').setup({
**Q: How do I override a preset?** **Q: How do I override a preset?**
```lua ```lua
local presets = require('preview.presets')
require('preview').setup({ require('preview').setup({
typst = vim.tbl_deep_extend('force', presets.typst, { typst = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } },
env = { TYPST_FONT_PATHS = '/usr/share/fonts' },
}),
}) })
``` ```
**Q: How do I automatically open the output file?** **Q: How do I automatically open the output file?**
Set `open = true` on your provider (all built-in presets have this enabled) to Set `open = true` on your provider (all built-in presets have this enabled) to
open the output with `vim.ui.open()` after the first successful compilation. open the output with `vim.ui.open()` after the first successful compilation. For
For a specific application, pass a command table: a specific application, pass a command table:
```lua ```lua
typst = vim.tbl_deep_extend('force', presets.typst, { require('preview').setup({
open = { 'sioyek', '--new-instance' }, typst = { open = { 'sioyek', '--new-instance' } },
}) })
``` ```
See `:h preview.nvim` for more information.

View file

@ -1,6 +1,6 @@
*preview.nvim.txt* Async document compilation for Neovim *preview.nvim.txt* Async document compilation for Neovim
Author: Raphael Author: Barrett Ruth <br.barrettruth@gmail.com>
License: MIT License: MIT
============================================================================== ==============================================================================
@ -20,19 +20,14 @@ REQUIREMENTS *preview.nvim-requirements
- A compiler binary for each configured provider (e.g. `typst`, `latexmk`) - A compiler binary for each configured provider (e.g. `typst`, `latexmk`)
============================================================================== ==============================================================================
INSTALLATION *preview.nvim-installation* SETUP *preview.nvim-setup*
With luarocks (recommended): Load preview.nvim with your package manager. For example, with lazy.nvim: >lua
>
:Rocks install preview.nvim
<
With lazy.nvim:
>lua
{ {
'barrettruth/preview.nvim', 'barrettruth/preview.nvim',
} }
< <
Call |preview.setup()| to configure providers before use.
============================================================================== ==============================================================================
CONFIGURATION *preview.nvim-configuration* CONFIGURATION *preview.nvim-configuration*
@ -42,9 +37,18 @@ Configure via `require('preview').setup()`.
*preview.setup()* *preview.setup()*
setup({opts?}) setup({opts?})
`opts` is a mixed table. Array entries are preset names (see `opts` is a table where keys are preset names or filetypes. For each
|preview.nvim-presets|). Hash entries with table values are custom key `k` with value `v` (excluding `debug`):
provider configs keyed by filetype.
- If `k` is a preset name and `v` is `true`, the preset is registered
as-is under its filetype.
- If `k` is a preset name and `v` is a table, it is deep-merged with
the preset and registered under the preset's filetype.
- If `k` is not a preset name and `v` is a table, it is registered
directly as a custom provider keyed by filetype `k`.
- If `v` is `false`, the entry is skipped (no-op).
See |preview.nvim-presets| for available preset names.
Fields:~ Fields:~
@ -91,33 +95,28 @@ Context fields:~
`root` string Project root (git root or file directory). `root` string Project root (git root or file directory).
`ft` string Filetype. `ft` string Filetype.
Example using preset names:~ Example enabling presets:~
>lua >lua
require('preview').setup({ 'typst', 'latex', 'markdown' }) require('preview').setup({ typst = true, latex = true, github = true })
< <
Example with a custom provider:~ Example overriding a preset field:~
>lua >lua
require('preview').setup({ require('preview').setup({
typst = { typst = { open = { 'sioyek', '--new-instance' } },
cmd = { 'typst', 'compile' }, })
<
Example with a fully custom provider (key is not a preset name):~
>lua
require('preview').setup({
rst = {
cmd = { 'rst2html' },
args = function(ctx) args = function(ctx)
return { ctx.file } return { ctx.file }
end, end,
output = function(ctx) output = function(ctx)
return ctx.file:gsub('%.typ$', '.pdf') return ctx.file:gsub('%.rst$', '.html')
end,
error_parser = function(stderr, ctx)
local diagnostics = {}
for line, col, msg in stderr:gmatch('error:.-(%d+):(%d+):%s*(.-)%\n') do
table.insert(diagnostics, {
lnum = tonumber(line) - 1,
col = tonumber(col) - 1,
message = msg,
severity = vim.diagnostic.severity.ERROR,
})
end
return diagnostics
end, end,
}, },
}) })
@ -132,20 +131,17 @@ Import them from `preview.presets`:
`presets.typst` typst compile → PDF `presets.typst` typst compile → PDF
`presets.latex` latexmk -pdf → PDF (with clean support) `presets.latex` latexmk -pdf → PDF (with clean support)
`presets.markdown` pandoc → HTML (standalone, embedded) `presets.markdown` pandoc → HTML (standalone, embedded)
`presets.github` pandoc → HTML (GitHub-styled) `presets.github` pandoc → HTML (GitHub-styled, `-f gfm` input)
Pass preset names as array entries to `setup()`: Enable presets with `preset_name = true`:
>lua >lua
require('preview').setup({ 'typst', 'latex', 'markdown' }) require('preview').setup({ typst = true, latex = true, github = true })
< <
Override individual fields using `vim.tbl_deep_extend`: Override individual fields by passing a table instead of `true`:
>lua >lua
local presets = require('preview.presets')
require('preview').setup({ require('preview').setup({
typst = vim.tbl_deep_extend('force', presets.typst, { typst = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } },
env = { TYPST_FONT_PATHS = '/usr/share/fonts' },
}),
}) })
< <
@ -160,7 +156,7 @@ COMMANDS *preview.nvim-commands*
`stop` Kill active compilation for the current buffer. `stop` Kill active compilation for the current buffer.
`clean` Run the provider's clean command. `clean` Run the provider's clean command.
`toggle` Toggle auto-compile on save for the current buffer. `toggle` Toggle auto-compile on save for the current buffer.
`status` Echo compilation status (idle, compiling, watching). `status` Echo compilation status (idle, compiling, toggled).
============================================================================== ==============================================================================
API *preview.nvim-api* API *preview.nvim-api*
@ -175,9 +171,9 @@ preview.clean({bufnr?}) *preview.clean()*
Run the provider's clean command for the buffer. Run the provider's clean command for the buffer.
preview.toggle({bufnr?}) *preview.toggle()* preview.toggle({bufnr?}) *preview.toggle()*
Toggle watch mode for the buffer. When enabled, the buffer is Toggle auto-compile for the buffer. When enabled, the buffer is
immediately compiled and automatically recompiled on each save immediately compiled and automatically recompiled on each save
(`BufWritePost`). Call again to stop watching. (`BufWritePost`). Call again to stop.
preview.status({bufnr?}) *preview.status()* preview.status({bufnr?}) *preview.status()*
Returns a |preview.Status| table. Returns a |preview.Status| table.
@ -186,7 +182,7 @@ preview.status({bufnr?}) *preview.status()*
Status fields:~ Status fields:~
`compiling` boolean Whether compilation is active. `compiling` boolean Whether compilation is active.
`watching` boolean Whether watch mode is active. `watching` boolean Whether auto-compile is active.
`provider` string? Name of the active provider. `provider` string? Name of the active provider.
`output_file` string? Path to the output file. `output_file` string? Path to the output file.
@ -207,12 +203,6 @@ preview.nvim fires User autocmds with structured data:
`PreviewCompileFailed` Compilation failed (non-zero exit). `PreviewCompileFailed` Compilation failed (non-zero exit).
data: `{ bufnr, provider, code, stderr }` data: `{ bufnr, provider, code, stderr }`
`PreviewWatchStarted` Watch mode enabled for a buffer.
data: `{ bufnr, provider }`
`PreviewWatchStopped` Watch mode disabled for a buffer.
data: `{ bufnr }`
Example:~ Example:~
>lua >lua
vim.api.nvim_create_autocmd('User', { vim.api.nvim_create_autocmd('User', {

View file

@ -188,11 +188,6 @@ function M.toggle(bufnr, name, provider, ctx_builder)
end, end,
}) })
vim.api.nvim_exec_autocmds('User', {
pattern = 'PreviewWatchStarted',
data = { bufnr = bufnr, provider = name },
})
M.compile(bufnr, name, provider, ctx_builder(bufnr)) M.compile(bufnr, name, provider, ctx_builder(bufnr))
end end
@ -205,11 +200,6 @@ function M.unwatch(bufnr)
vim.api.nvim_del_autocmd(au_id) vim.api.nvim_del_autocmd(au_id)
watching[bufnr] = nil watching[bufnr] = nil
log.dbg('unwatched buffer %d', bufnr) log.dbg('unwatched buffer %d', bufnr)
vim.api.nvim_exec_autocmds('User', {
pattern = 'PreviewWatchStopped',
data = { bufnr = bufnr },
})
end end
---@param bufnr integer ---@param bufnr integer

View file

@ -68,15 +68,17 @@ function M.setup(opts)
if k == 'debug' then if k == 'debug' then
vim.validate('preview.setup opts.debug', v, { 'boolean', 'string' }) vim.validate('preview.setup opts.debug', v, { 'boolean', 'string' })
debug = v debug = v
elseif type(k) == 'number' then elseif type(k) ~= 'number' then
vim.validate('preview.setup preset name', v, 'string') local preset = presets[k]
local preset = presets[v]
if preset then if preset then
providers[preset.ft] = preset if v == true then
providers[preset.ft] = preset
elseif type(v) == 'table' then
providers[preset.ft] = vim.tbl_deep_extend('force', preset, v)
end
elseif type(v) == 'table' then
providers[k] = v
end end
else
vim.validate('preview.setup provider config', v, 'table')
providers[k] = v
end end
end end

View file

@ -8,7 +8,7 @@ M.typst = {
return { ctx.file } return { ctx.file }
end, end,
output = function(ctx) output = function(ctx)
return ctx.file:gsub('%.typ$', '.pdf') return (ctx.file:gsub('%.typ$', '.pdf'))
end, end,
open = { 'xdg-open' }, open = { 'xdg-open' },
} }
@ -21,7 +21,7 @@ M.latex = {
return { '-pdf', '-interaction=nonstopmode', ctx.file } return { '-pdf', '-interaction=nonstopmode', ctx.file }
end, end,
output = function(ctx) output = function(ctx)
return ctx.file:gsub('%.tex$', '.pdf') return (ctx.file:gsub('%.tex$', '.pdf'))
end, end,
clean = function(ctx) clean = function(ctx)
return { 'latexmk', '-c', ctx.file } return { 'latexmk', '-c', ctx.file }
@ -38,7 +38,7 @@ M.markdown = {
return { ctx.file, '-s', '--embed-resources', '-o', output } return { ctx.file, '-s', '--embed-resources', '-o', output }
end, end,
output = function(ctx) output = function(ctx)
return ctx.file:gsub('%.md$', '.html') return (ctx.file:gsub('%.md$', '.html'))
end, end,
clean = function(ctx) clean = function(ctx)
return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) } return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) }
@ -53,17 +53,19 @@ M.github = {
args = function(ctx) args = function(ctx)
local output = ctx.file:gsub('%.md$', '.html') local output = ctx.file:gsub('%.md$', '.html')
return { return {
'-f',
'gfm',
ctx.file, ctx.file,
'-s', '-s',
'--embed-resources', '--embed-resources',
'--css', '--css',
'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.css', 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/dist/gfm.css',
'-o', '-o',
output, output,
} }
end, end,
output = function(ctx) output = function(ctx)
return ctx.file:gsub('%.md$', '.html') return (ctx.file:gsub('%.md$', '.html'))
end, end,
clean = function(ctx) clean = function(ctx)
return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) } return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) }

View file

@ -190,31 +190,6 @@ describe('compiler', function()
helpers.delete_buffer(bufnr) helpers.delete_buffer(bufnr)
end) end)
it('fires PreviewWatchStarted event', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_event.txt')
local fired = false
vim.api.nvim_create_autocmd('User', {
pattern = 'PreviewWatchStarted',
once = true,
callback = function()
fired = true
end,
})
local provider = { cmd = { 'echo', 'ok' } }
local ctx_builder = function(b)
return { bufnr = b, file = '/tmp/preview_test_watch_event.txt', root = '/tmp', ft = 'text' }
end
compiler.toggle(bufnr, 'echo', provider, ctx_builder)
assert.is_true(fired)
compiler.unwatch(bufnr)
helpers.delete_buffer(bufnr)
end)
it('toggles off when called again', function() it('toggles off when called again', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text') local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_toggle.txt') vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_toggle.txt')
@ -233,32 +208,6 @@ describe('compiler', function()
helpers.delete_buffer(bufnr) helpers.delete_buffer(bufnr)
end) end)
it('fires PreviewWatchStopped on unwatch', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_stop.txt')
local stopped = false
vim.api.nvim_create_autocmd('User', {
pattern = 'PreviewWatchStopped',
once = true,
callback = function()
stopped = true
end,
})
local provider = { cmd = { 'echo', 'ok' } }
local ctx_builder = function(b)
return { bufnr = b, file = '/tmp/preview_test_watch_stop.txt', root = '/tmp', ft = 'text' }
end
compiler.toggle(bufnr, 'echo', provider, ctx_builder)
compiler.unwatch(bufnr)
assert.is_true(stopped)
assert.is_nil(compiler._test.watching[bufnr])
helpers.delete_buffer(bufnr)
end)
it('stop_all clears watches', function() it('stop_all clears watches', function()
local bufnr = helpers.create_buffer({ 'hello' }, 'text') local bufnr = helpers.create_buffer({ 'hello' }, 'text')
vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_stopall.txt') vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch_stopall.txt')

View file

@ -22,7 +22,7 @@ describe('preview', function()
assert.are.same({}, config.providers) assert.are.same({}, config.providers)
end) end)
it('accepts full provider config via hash entry', function() it('merges override table with matching preset', function()
helpers.reset_config({ helpers.reset_config({
typst = { typst = {
cmd = { 'typst', 'compile' }, cmd = { 'typst', 'compile' },
@ -33,8 +33,8 @@ describe('preview', function()
assert.is_not_nil(config.providers.typst) assert.is_not_nil(config.providers.typst)
end) end)
it('resolves array preset names to provider configs', function() it('resolves preset = true to provider config', function()
helpers.reset_config({ 'typst', 'markdown' }) helpers.reset_config({ typst = true, markdown = true })
local config = require('preview').get_config() local config = require('preview').get_config()
local presets = require('preview.presets') local presets = require('preview.presets')
assert.are.same(presets.typst, config.providers.typst) assert.are.same(presets.typst, config.providers.typst)
@ -42,14 +42,14 @@ describe('preview', function()
end) end)
it('resolves latex preset under tex filetype', function() it('resolves latex preset under tex filetype', function()
helpers.reset_config({ 'latex' }) helpers.reset_config({ latex = true })
local config = require('preview').get_config() local config = require('preview').get_config()
local presets = require('preview.presets') local presets = require('preview.presets')
assert.are.same(presets.latex, config.providers.tex) assert.are.same(presets.latex, config.providers.tex)
end) end)
it('resolves github preset under markdown filetype', function() it('resolves github preset under markdown filetype', function()
helpers.reset_config({ 'github' }) helpers.reset_config({ github = true })
local config = require('preview').get_config() local config = require('preview').get_config()
local presets = require('preview.presets') local presets = require('preview.presets')
assert.are.same(presets.github, config.providers.markdown) assert.are.same(presets.github, config.providers.markdown)
@ -59,7 +59,7 @@ describe('preview', function()
describe('resolve_provider', function() describe('resolve_provider', function()
before_each(function() before_each(function()
helpers.reset_config({ helpers.reset_config({
typst = { cmd = { 'typst', 'compile' } }, typst = true,
}) })
preview = require('preview') preview = require('preview')
end) end)

View file

@ -139,16 +139,31 @@ describe('presets', function()
local args = presets.github.args(md_ctx) local args = presets.github.args(md_ctx)
assert.is_table(args) assert.is_table(args)
assert.are.same({ assert.are.same({
'-f',
'gfm',
'/tmp/document.md', '/tmp/document.md',
'-s', '-s',
'--embed-resources', '--embed-resources',
'--css', '--css',
'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.css', 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/dist/gfm.css',
'-o', '-o',
'/tmp/document.html', '/tmp/document.html',
}, args) }, args)
end) end)
it('args include -f and gfm flags', function()
local args = presets.github.args(md_ctx)
local idx = nil
for i, v in ipairs(args) do
if v == '-f' then
idx = i
break
end
end
assert.is_not_nil(idx)
assert.are.equal('gfm', args[idx + 1])
end)
it('returns html output path', function() it('returns html output path', function()
local output = presets.github.output(md_ctx) local output = presets.github.output(md_ctx)
assert.is_string(output) assert.is_string(output)