Compare commits

..

No commits in common. "50580ecde51eb576501b376b8c423e560b4424c8" and "673573044f1ba2ef840060b25e3187565d9382b3" have entirely different histories.

8 changed files with 138 additions and 81 deletions

View file

@ -55,19 +55,24 @@ 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 = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } }, typst = vim.tbl_deep_extend('force', presets.typst, {
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. For open the output with `vim.ui.open()` after the first successful compilation.
a specific application, pass a command table: For a specific application, pass a command table:
```lua ```lua
require('preview').setup({ typst = vim.tbl_deep_extend('force', presets.typst, {
typst = { open = { 'sioyek', '--new-instance' } }, 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: Barrett Ruth <br.barrettruth@gmail.com> Author: Raphael
License: MIT License: MIT
============================================================================== ==============================================================================
@ -20,14 +20,19 @@ 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`)
============================================================================== ==============================================================================
SETUP *preview.nvim-setup* INSTALLATION *preview.nvim-installation*
Load preview.nvim with your package manager. For example, with lazy.nvim: >lua With luarocks (recommended):
>
: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*
@ -37,18 +42,9 @@ Configure via `require('preview').setup()`.
*preview.setup()* *preview.setup()*
setup({opts?}) setup({opts?})
`opts` is a table where keys are preset names or filetypes. For each `opts` is a mixed table. Array entries are preset names (see
key `k` with value `v` (excluding `debug`): |preview.nvim-presets|). Hash entries with table values are custom
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:~
@ -95,28 +91,33 @@ 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 enabling presets:~ Example using preset names:~
>lua >lua
require('preview').setup({ typst = true, latex = true, github = true }) require('preview').setup({ 'typst', 'latex', 'markdown' })
< <
Example overriding a preset field:~ Example with a custom provider:~
>lua >lua
require('preview').setup({ require('preview').setup({
typst = { open = { 'sioyek', '--new-instance' } }, typst = {
}) 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('%.rst$', '.html') return ctx.file:gsub('%.typ$', '.pdf')
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,
}, },
}) })
@ -131,17 +132,20 @@ 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, `-f gfm` input) `presets.github` pandoc → HTML (GitHub-styled)
Enable presets with `preset_name = true`: Pass preset names as array entries to `setup()`:
>lua >lua
require('preview').setup({ typst = true, latex = true, github = true }) require('preview').setup({ 'typst', 'latex', 'markdown' })
< <
Override individual fields by passing a table instead of `true`: Override individual fields using `vim.tbl_deep_extend`:
>lua >lua
local presets = require('preview.presets')
require('preview').setup({ require('preview').setup({
typst = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } }, typst = vim.tbl_deep_extend('force', presets.typst, {
env = { TYPST_FONT_PATHS = '/usr/share/fonts' },
}),
}) })
< <
@ -156,7 +160,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, toggled). `status` Echo compilation status (idle, compiling, watching).
============================================================================== ==============================================================================
API *preview.nvim-api* API *preview.nvim-api*
@ -171,9 +175,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 auto-compile for the buffer. When enabled, the buffer is Toggle watch mode 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. (`BufWritePost`). Call again to stop watching.
preview.status({bufnr?}) *preview.status()* preview.status({bufnr?}) *preview.status()*
Returns a |preview.Status| table. Returns a |preview.Status| table.
@ -182,7 +186,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 auto-compile is active. `watching` boolean Whether watch mode 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.
@ -203,6 +207,12 @@ 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,6 +188,11 @@ 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
@ -200,6 +205,11 @@ 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,19 +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
local preset = presets[k] vim.validate('preview.setup preset name', v, 'string')
local preset = presets[v]
if preset then if preset then
if v == true then
providers[preset.ft] = preset providers[preset.ft] = preset
elseif type(v) == 'table' then
providers[preset.ft] = vim.tbl_deep_extend('force', preset, v)
end end
elseif type(v) == 'table' then else
vim.validate('preview.setup provider config', v, 'table')
providers[k] = v providers[k] = v
end end
end end
end
config = vim.tbl_deep_extend('force', default_config, { config = vim.tbl_deep_extend('force', default_config, {
debug = debug, debug = debug,

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,19 +53,17 @@ 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/dist/gfm.css', 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.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,6 +190,31 @@ 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')
@ -208,6 +233,32 @@ 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('merges override table with matching preset', function() it('accepts full provider config via hash entry', 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 preset = true to provider config', function() it('resolves array preset names to provider configs', function()
helpers.reset_config({ typst = true, markdown = true }) helpers.reset_config({ 'typst', 'markdown' })
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 = true }) helpers.reset_config({ 'latex' })
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 = true }) helpers.reset_config({ 'github' })
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 = true, typst = { cmd = { 'typst', 'compile' } },
}) })
preview = require('preview') preview = require('preview')
end) end)

View file

@ -139,31 +139,16 @@ 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/dist/gfm.css', 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.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)