From 673573044f1ba2ef840060b25e3187565d9382b3 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 23:37:44 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20rename=20watch=20=E2=86=92=20toggle,=20?= =?UTF-8?q?auto-compile=20on=20start,=20built-in=20opener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: :Preview watch only registered a BufWritePost autocmd without compiling immediately, required boilerplate to open output files after first compilation, and was misleadingly named. Solution: Rename watch → toggle throughout. M.toggle now compiles immediately on activation. Add an open field to ProviderConfig: true calls vim.ui.open(), a string[] runs the command with the output path appended, tracked per-buffer so the file opens only once. All presets default to { 'xdg-open' }. Health check validates opener binaries. Guard the async compile callback against invalid buffer ids. --- README.md | 102 ++++++++++++++++++--------------- doc/preview.nvim.txt | 120 +++++++++++++++++++-------------------- lua/preview/commands.lua | 8 +-- lua/preview/compiler.lua | 22 ++++++- lua/preview/health.lua | 8 +++ lua/preview/init.lua | 63 ++++++++++---------- lua/preview/presets.lua | 40 ++++++++++++- spec/commands_spec.lua | 4 +- spec/compiler_spec.lua | 16 +++--- spec/helpers.lua | 7 ++- spec/init_spec.lua | 44 ++++++++------ spec/presets_spec.lua | 88 ++++++++++++++++++++++++++-- 12 files changed, 346 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index eefe9a6..dcdf689 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # preview.nvim -Async document compilation for Neovim. +**Async document compilation for Neovim** -A framework for compiling documents (LaTeX, Typst, Markdown, etc.) -asynchronously with error diagnostics. Ships with zero defaults — you configure -your own providers. +An extensible framework for compiling documents (LaTeX, Typst, Markdown, etc.) +asynchronously with error diagnostics. ## Features @@ -12,59 +11,68 @@ your own providers. - Compiler errors as native `vim.diagnostic` - User events for extensibility (`PreviewCompileStarted`, `PreviewCompileSuccess`, `PreviewCompileFailed`) +- Built-in presets for Typst, LaTeX, Markdown, and GitHub-flavored Markdown - `:checkhealth` integration - Zero dependencies beyond Neovim 0.11.0+ ## Requirements -- Neovim >= 0.11.0 -- A compiler binary for each provider you configure +- Neovim 0.11.0+ ## Installation -```lua --- lazy.nvim -{ 'barrettruth/preview.nvim' } +Install with your package manager of choice or via +[luarocks](https://luarocks.org/modules/barrettruth/preview.nvim): + ``` - -```vim -" luarocks -:Rocks install preview.nvim -``` - -## Configuration - -Use built-in presets for common tools: - -```lua -local presets = require('preview.presets') -vim.g.preview = { - providers = { - typst = presets.typst, - tex = presets.latex, - markdown = presets.markdown, - }, -} -``` - -Or define providers manually: - -```lua -vim.g.preview = { - providers = { - typst = { - cmd = { 'typst', 'compile' }, - args = function(ctx) - return { ctx.file } - end, - output = function(ctx) - return ctx.file:gsub('%.typ$', '.pdf') - end, - }, - }, -} +luarocks install preview.nvim ``` ## Documentation -See `:help preview.nvim` for full documentation. +```vim +:help preview.nvim +``` + +## FAQ + +**Q: How do I define a custom provider?** + +```lua +require('preview').setup({ + typst = { + cmd = { 'typst', 'compile' }, + args = function(ctx) + return { ctx.file } + end, + output = function(ctx) + return ctx.file:gsub('%.typ$', '.pdf') + end, + }, +}) +``` + +**Q: How do I override a preset?** + +```lua +local presets = require('preview.presets') +require('preview').setup({ + typst = vim.tbl_deep_extend('force', presets.typst, { + env = { TYPST_FONT_PATHS = '/usr/share/fonts' }, + }), +}) +``` + +**Q: How do I automatically open the output file?** + +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 a specific application, pass a command table: + +```lua +typst = vim.tbl_deep_extend('force', presets.typst, { + open = { 'sioyek', '--new-instance' }, +}) +``` + +See `:h preview.nvim` for more information. diff --git a/doc/preview.nvim.txt b/doc/preview.nvim.txt index d48daf6..3ed587a 100644 --- a/doc/preview.nvim.txt +++ b/doc/preview.nvim.txt @@ -37,18 +37,21 @@ With lazy.nvim: ============================================================================== CONFIGURATION *preview.nvim-configuration* -Configure via the `vim.g.preview` global table before the plugin loads. +Configure via `require('preview').setup()`. - *preview.Config* -Fields:~ + *preview.setup()* +setup({opts?}) + + `opts` is a mixed table. Array entries are preset names (see + |preview.nvim-presets|). Hash entries with table values are custom + provider configs keyed by filetype. + + Fields:~ `debug` boolean|string Enable debug logging. A string value is treated as a log file path. Default: `false` - `providers` table Provider configurations keyed by - filetype. Default: `{}` - *preview.ProviderConfig* Provider fields:~ @@ -74,6 +77,12 @@ Provider fields:~ If a function, receives a |preview.Context|. + `open` boolean|string[] Open the output file after the first + 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:~ @@ -82,38 +91,36 @@ Context fields:~ `root` string Project root (git root or file directory). `ft` string Filetype. -Example:~ +Example using preset names:~ >lua - vim.g.preview = { - providers = { - typst = { - cmd = { 'typst', 'compile' }, - args = function(ctx) - return { ctx.file } - end, - output = function(ctx) - 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, - }, - tex = { - cmd = { 'latexmk' }, - args = { '-pdf', '-interaction=nonstopmode' }, - clean = { 'latexmk', '-c' }, - }, + require('preview').setup({ 'typst', 'latex', 'markdown' }) +< + +Example with a custom provider:~ +>lua + require('preview').setup({ + typst = { + cmd = { 'typst', 'compile' }, + args = function(ctx) + return { ctx.file } + end, + output = function(ctx) + 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, }, - } + }) < ============================================================================== @@ -122,32 +129,24 @@ PRESETS *preview.nvim-presets* preview.nvim ships with pre-built provider configurations for common tools. Import them from `preview.presets`: - `presets.typst` typst compile → PDF - `presets.latex` latexmk -pdf → PDF (with clean support) - `presets.markdown` pandoc → PDF + `presets.typst` typst compile → PDF + `presets.latex` latexmk -pdf → PDF (with clean support) + `presets.markdown` pandoc → HTML (standalone, embedded) + `presets.github` pandoc → HTML (GitHub-styled) -Example:~ +Pass preset names as array entries to `setup()`: >lua - local presets = require('preview.presets') - vim.g.preview = { - providers = { - typst = presets.typst, - tex = presets.latex, - markdown = presets.markdown, - }, - } + require('preview').setup({ 'typst', 'latex', 'markdown' }) < -Override individual fields with `vim.tbl_deep_extend`: +Override individual fields using `vim.tbl_deep_extend`: >lua local presets = require('preview.presets') - vim.g.preview = { - providers = { - typst = vim.tbl_deep_extend('force', presets.typst, { - env = { TYPST_FONT_PATHS = '/usr/share/fonts' }, - }), - }, - } + require('preview').setup({ + typst = vim.tbl_deep_extend('force', presets.typst, { + env = { TYPST_FONT_PATHS = '/usr/share/fonts' }, + }), + }) < ============================================================================== @@ -160,7 +159,7 @@ COMMANDS *preview.nvim-commands* `compile` Compile the current buffer (default if omitted). `stop` Kill active compilation for the current buffer. `clean` Run the provider's clean command. - `watch` 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). ============================================================================== @@ -175,10 +174,10 @@ preview.stop({bufnr?}) *preview.stop()* preview.clean({bufnr?}) *preview.clean()* Run the provider's clean command for the buffer. -preview.watch({bufnr?}) *preview.watch()* +preview.toggle({bufnr?}) *preview.toggle()* Toggle watch mode for the buffer. When enabled, the buffer is - automatically compiled on each save (`BufWritePost`). Call again - to stop watching. + immediately compiled and automatically recompiled on each save + (`BufWritePost`). Call again to stop watching. preview.status({bufnr?}) *preview.status()* Returns a |preview.Status| table. @@ -232,6 +231,7 @@ Run `:checkhealth preview` to verify: - Neovim version >= 0.11.0 - Each configured provider's binary is executable +- Each configured provider's opener binary (if any) is executable - Each configured provider's filetype mapping is valid ============================================================================== diff --git a/lua/preview/commands.lua b/lua/preview/commands.lua index 0545a86..8e08236 100644 --- a/lua/preview/commands.lua +++ b/lua/preview/commands.lua @@ -1,6 +1,6 @@ local M = {} -local subcommands = { 'compile', 'stop', 'clean', 'watch', 'status' } +local subcommands = { 'compile', 'stop', 'clean', 'toggle', 'status' } ---@param args string local function dispatch(args) @@ -12,8 +12,8 @@ local function dispatch(args) require('preview').stop() elseif subcmd == 'clean' then require('preview').clean() - elseif subcmd == 'watch' then - require('preview').watch() + elseif subcmd == 'toggle' then + require('preview').toggle() elseif subcmd == 'status' then local s = require('preview').status() local parts = {} @@ -47,7 +47,7 @@ function M.setup() complete = function(lead) return complete(lead) end, - desc = 'Compile, stop, clean, watch, or check status of document preview', + desc = 'Compile, stop, clean, toggle, or check status of document preview', }) end diff --git a/lua/preview/compiler.lua b/lua/preview/compiler.lua index dc1a564..6907524 100644 --- a/lua/preview/compiler.lua +++ b/lua/preview/compiler.lua @@ -9,6 +9,9 @@ local active = {} ---@type table local watching = {} +---@type table +local opened = {} + ---@param val string[]|fun(ctx: preview.Context): string[] ---@param ctx preview.Context ---@return string[] @@ -68,6 +71,9 @@ function M.compile(bufnr, name, provider, ctx) }, vim.schedule_wrap(function(result) active[bufnr] = nil + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end if result.code == 0 then log.dbg('compilation succeeded for buffer %d', bufnr) @@ -76,6 +82,16 @@ function M.compile(bufnr, name, provider, ctx) pattern = 'PreviewCompileSuccess', data = { bufnr = bufnr, provider = name, output = output_file }, }) + if provider.open and not opened[bufnr] and output_file ~= '' then + if provider.open == true then + vim.ui.open(output_file) + elseif type(provider.open) == 'table' then + local open_cmd = vim.list_extend({}, provider.open) + table.insert(open_cmd, output_file) + vim.system(open_cmd) + end + opened[bufnr] = true + end else log.dbg('compilation failed for buffer %d (exit code %d)', bufnr, result.code) if provider.error_parser then @@ -146,7 +162,7 @@ end ---@param name string ---@param provider preview.ProviderConfig ---@param ctx_builder fun(bufnr: integer): preview.Context -function M.watch(bufnr, name, provider, ctx_builder) +function M.toggle(bufnr, name, provider, ctx_builder) if watching[bufnr] then M.unwatch(bufnr) return @@ -168,6 +184,7 @@ function M.watch(bufnr, name, provider, ctx_builder) once = true, callback = function() M.unwatch(bufnr) + opened[bufnr] = nil end, }) @@ -175,6 +192,8 @@ function M.watch(bufnr, name, provider, ctx_builder) pattern = 'PreviewWatchStarted', data = { bufnr = bufnr, provider = name }, }) + + M.compile(bufnr, name, provider, ctx_builder(bufnr)) end ---@param bufnr integer @@ -244,6 +263,7 @@ end M._test = { active = active, watching = watching, + opened = opened, } return M diff --git a/lua/preview/health.lua b/lua/preview/health.lua index 251dbfa..196100d 100644 --- a/lua/preview/health.lua +++ b/lua/preview/health.lua @@ -25,6 +25,14 @@ function M.check() else vim.health.error('filetype "' .. ft .. '": ' .. bin .. ' not found') end + if type(provider.open) == 'table' then + local opener = provider.open[1] + if vim.fn.executable(opener) == 1 then + vim.health.ok('filetype "' .. ft .. '": opener ' .. opener .. ' found') + else + vim.health.error('filetype "' .. ft .. '": opener ' .. opener .. ' not found') + end + end end end diff --git a/lua/preview/init.lua b/lua/preview/init.lua index 1030bf7..968b201 100644 --- a/lua/preview/init.lua +++ b/lua/preview/init.lua @@ -1,4 +1,5 @@ ---@class preview.ProviderConfig +---@field ft? string ---@field cmd string[] ---@field args? string[]|fun(ctx: preview.Context): string[] ---@field cwd? string|fun(ctx: preview.Context): string @@ -6,6 +7,7 @@ ---@field output? string|fun(ctx: preview.Context): string ---@field error_parser? fun(stderr: string, ctx: preview.Context): preview.Diagnostic[] ---@field clean? string[]|fun(ctx: preview.Context): string[] +---@field open? boolean|string[] ---@class preview.Config ---@field debug boolean|string @@ -32,10 +34,11 @@ ---@field output_file string ---@class preview +---@field setup fun(opts?: table) ---@field compile fun(bufnr?: integer) ---@field stop fun(bufnr?: integer) ---@field clean fun(bufnr?: integer) ----@field watch fun(bufnr?: integer) +---@field toggle fun(bufnr?: integer) ---@field status fun(bufnr?: integer): preview.Status ---@field get_config fun(): preview.Config local M = {} @@ -52,39 +55,48 @@ local default_config = { ---@type preview.Config local config = vim.deepcopy(default_config) -local initialized = false +---@param opts? table +function M.setup(opts) + opts = opts or {} + vim.validate('preview.setup opts', opts, 'table') -local function init() - if initialized then - return - end - initialized = true + local presets = require('preview.presets') + local providers = {} + local debug = false - local opts = vim.g.preview or {} - - vim.validate('preview config', opts, 'table') - if opts.debug ~= nil then - vim.validate('preview config.debug', opts.debug, { 'boolean', 'string' }) - end - if opts.providers ~= nil then - vim.validate('preview config.providers', opts.providers, 'table') + for k, v in pairs(opts) do + if k == 'debug' then + vim.validate('preview.setup opts.debug', v, { 'boolean', 'string' }) + debug = v + elseif type(k) == 'number' then + vim.validate('preview.setup preset name', v, 'string') + local preset = presets[v] + if preset then + providers[preset.ft] = preset + end + else + vim.validate('preview.setup provider config', v, 'table') + providers[k] = v + end end - config = vim.tbl_deep_extend('force', default_config, opts) + config = vim.tbl_deep_extend('force', default_config, { + debug = debug, + providers = providers, + }) + log.set_enabled(config.debug) log.dbg('initialized with %d providers', vim.tbl_count(config.providers)) end ---@return preview.Config function M.get_config() - init() return config end ---@param bufnr? integer ---@return string? function M.resolve_provider(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() local ft = vim.bo[bufnr].filetype if not config.providers[ft] then @@ -97,7 +109,6 @@ end ---@param bufnr? integer ---@return preview.Context function M.build_context(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() local file = vim.api.nvim_buf_get_name(bufnr) local root = vim.fs.root(bufnr, { '.git' }) or vim.fn.fnamemodify(file, ':h') @@ -111,42 +122,38 @@ end ---@param bufnr? integer function M.compile(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() local name = M.resolve_provider(bufnr) if not name then vim.notify('[preview.nvim] no provider configured for this filetype', vim.log.levels.WARN) return end - local provider = config.providers[name] local ctx = M.build_context(bufnr) + local provider = config.providers[name] compiler.compile(bufnr, name, provider, ctx) end ---@param bufnr? integer function M.stop(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() compiler.stop(bufnr) end ---@param bufnr? integer function M.clean(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() local name = M.resolve_provider(bufnr) if not name then vim.notify('[preview.nvim] no provider configured for this filetype', vim.log.levels.WARN) return end - local provider = config.providers[name] local ctx = M.build_context(bufnr) + local provider = config.providers[name] compiler.clean(bufnr, name, provider, ctx) end ---@param bufnr? integer -function M.watch(bufnr) - init() +function M.toggle(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local name = M.resolve_provider(bufnr) if not name then @@ -154,7 +161,7 @@ function M.watch(bufnr) return end local provider = config.providers[name] - compiler.watch(bufnr, name, provider, M.build_context) + compiler.toggle(bufnr, name, provider, M.build_context) end ---@class preview.Status @@ -166,7 +173,6 @@ end ---@param bufnr? integer ---@return preview.Status function M.status(bufnr) - init() bufnr = bufnr or vim.api.nvim_get_current_buf() return compiler.status(bufnr) end @@ -174,7 +180,6 @@ end M._test = { ---@diagnostic disable-next-line: assign-type-mismatch reset = function() - initialized = false config = vim.deepcopy(default_config) end, } diff --git a/lua/preview/presets.lua b/lua/preview/presets.lua index 124d00e..3579a8d 100644 --- a/lua/preview/presets.lua +++ b/lua/preview/presets.lua @@ -2,6 +2,7 @@ local M = {} ---@type preview.ProviderConfig M.typst = { + ft = 'typst', cmd = { 'typst', 'compile' }, args = function(ctx) return { ctx.file } @@ -9,10 +10,12 @@ M.typst = { output = function(ctx) return ctx.file:gsub('%.typ$', '.pdf') end, + open = { 'xdg-open' }, } ---@type preview.ProviderConfig M.latex = { + ft = 'tex', cmd = { 'latexmk' }, args = function(ctx) return { '-pdf', '-interaction=nonstopmode', ctx.file } @@ -23,18 +26,49 @@ M.latex = { clean = function(ctx) return { 'latexmk', '-c', ctx.file } end, + open = { 'xdg-open' }, } ---@type preview.ProviderConfig M.markdown = { + ft = 'markdown', cmd = { 'pandoc' }, args = function(ctx) - local output = ctx.file:gsub('%.md$', '.pdf') - return { ctx.file, '-o', output } + local output = ctx.file:gsub('%.md$', '.html') + return { ctx.file, '-s', '--embed-resources', '-o', output } end, output = function(ctx) - return ctx.file:gsub('%.md$', '.pdf') + return ctx.file:gsub('%.md$', '.html') end, + clean = function(ctx) + return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) } + end, + open = { 'xdg-open' }, +} + +---@type preview.ProviderConfig +M.github = { + ft = 'markdown', + cmd = { 'pandoc' }, + args = function(ctx) + local output = ctx.file:gsub('%.md$', '.html') + return { + ctx.file, + '-s', + '--embed-resources', + '--css', + 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.css', + '-o', + output, + } + end, + output = function(ctx) + return ctx.file:gsub('%.md$', '.html') + end, + clean = function(ctx) + return { 'rm', '-f', (ctx.file:gsub('%.md$', '.html')) } + end, + open = { 'xdg-open' }, } return M diff --git a/spec/commands_spec.lua b/spec/commands_spec.lua index c0615d1..be9a616 100644 --- a/spec/commands_spec.lua +++ b/spec/commands_spec.lua @@ -35,10 +35,10 @@ describe('commands', function() end) end) - it('does not error on :Preview watch with no provider', function() + it('does not error on :Preview toggle with no provider', function() require('preview.commands').setup() assert.has_no.errors(function() - vim.cmd('Preview watch') + vim.cmd('Preview toggle') end) end) end) diff --git a/spec/compiler_spec.lua b/spec/compiler_spec.lua index 23ed118..7360ceb 100644 --- a/spec/compiler_spec.lua +++ b/spec/compiler_spec.lua @@ -174,7 +174,7 @@ describe('compiler', function() end) end) - describe('watch', function() + describe('toggle', function() it('registers autocmd and tracks in watching table', function() local bufnr = helpers.create_buffer({ 'hello' }, 'text') vim.api.nvim_buf_set_name(bufnr, '/tmp/preview_test_watch.txt') @@ -184,7 +184,7 @@ describe('compiler', function() return { bufnr = b, file = '/tmp/preview_test_watch.txt', root = '/tmp', ft = 'text' } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) assert.is_not_nil(compiler._test.watching[bufnr]) helpers.delete_buffer(bufnr) @@ -208,7 +208,7 @@ describe('compiler', function() return { bufnr = b, file = '/tmp/preview_test_watch_event.txt', root = '/tmp', ft = 'text' } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) assert.is_true(fired) compiler.unwatch(bufnr) @@ -224,10 +224,10 @@ describe('compiler', function() return { bufnr = b, file = '/tmp/preview_test_watch_toggle.txt', root = '/tmp', ft = 'text' } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) assert.is_not_nil(compiler._test.watching[bufnr]) - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) assert.is_nil(compiler._test.watching[bufnr]) helpers.delete_buffer(bufnr) @@ -251,7 +251,7 @@ describe('compiler', function() return { bufnr = b, file = '/tmp/preview_test_watch_stop.txt', root = '/tmp', ft = 'text' } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) compiler.unwatch(bufnr) assert.is_true(stopped) assert.is_nil(compiler._test.watching[bufnr]) @@ -273,7 +273,7 @@ describe('compiler', function() } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) assert.is_not_nil(compiler._test.watching[bufnr]) compiler.stop_all() @@ -294,7 +294,7 @@ describe('compiler', function() return { bufnr = b, file = '/tmp/preview_test_watch_status.txt', root = '/tmp', ft = 'text' } end - compiler.watch(bufnr, 'echo', provider, ctx_builder) + compiler.toggle(bufnr, 'echo', provider, ctx_builder) s = compiler.status(bufnr) assert.is_true(s.watching) diff --git a/spec/helpers.lua b/spec/helpers.lua index 9790250..dd1ed7e 100644 --- a/spec/helpers.lua +++ b/spec/helpers.lua @@ -20,8 +20,11 @@ function M.delete_buffer(bufnr) end function M.reset_config(opts) - vim.g.preview = opts - require('preview')._test.reset() + local preview = require('preview') + preview._test.reset() + if opts then + preview.setup(opts) + end end return M diff --git a/spec/init_spec.lua b/spec/init_spec.lua index 3316b63..7921aeb 100644 --- a/spec/init_spec.lua +++ b/spec/init_spec.lua @@ -9,13 +9,7 @@ describe('preview', function() end) describe('config', function() - it('accepts nil config', function() - assert.has_no.errors(function() - preview.get_config() - end) - end) - - it('applies default values', function() + it('returns defaults before setup is called', function() local config = preview.get_config() assert.is_false(config.debug) assert.are.same({}, config.providers) @@ -28,26 +22,44 @@ describe('preview', function() assert.are.same({}, config.providers) end) - it('accepts full provider config', function() + it('accepts full provider config via hash entry', function() helpers.reset_config({ - providers = { - typst = { - cmd = { 'typst', 'compile' }, - args = { '%s' }, - }, + typst = { + cmd = { 'typst', 'compile' }, + args = { '%s' }, }, }) local config = require('preview').get_config() assert.is_not_nil(config.providers.typst) end) + + it('resolves array preset names to provider configs', function() + helpers.reset_config({ 'typst', 'markdown' }) + local config = require('preview').get_config() + local presets = require('preview.presets') + assert.are.same(presets.typst, config.providers.typst) + assert.are.same(presets.markdown, config.providers.markdown) + end) + + it('resolves latex preset under tex filetype', function() + helpers.reset_config({ 'latex' }) + local config = require('preview').get_config() + local presets = require('preview.presets') + assert.are.same(presets.latex, config.providers.tex) + end) + + it('resolves github preset under markdown filetype', function() + helpers.reset_config({ 'github' }) + local config = require('preview').get_config() + local presets = require('preview.presets') + assert.are.same(presets.github, config.providers.markdown) + end) end) describe('resolve_provider', function() before_each(function() helpers.reset_config({ - providers = { - typst = { cmd = { 'typst', 'compile' } }, - }, + typst = { cmd = { 'typst', 'compile' } }, }) preview = require('preview') end) diff --git a/spec/presets_spec.lua b/spec/presets_spec.lua index 7f137cb..2eba98f 100644 --- a/spec/presets_spec.lua +++ b/spec/presets_spec.lua @@ -13,6 +13,10 @@ describe('presets', function() } describe('typst', function() + it('has ft', function() + assert.are.equal('typst', presets.typst.ft) + end) + it('has cmd', function() assert.are.same({ 'typst', 'compile' }, presets.typst.cmd) end) @@ -28,6 +32,10 @@ describe('presets', function() assert.is_string(output) assert.are.equal('/tmp/document.pdf', output) end) + + it('has open enabled', function() + assert.are.same({ 'xdg-open' }, presets.typst.open) + end) end) describe('latex', function() @@ -38,6 +46,10 @@ describe('presets', function() ft = 'tex', } + it('has ft', function() + assert.are.equal('tex', presets.latex.ft) + end) + it('has cmd', function() assert.are.same({ 'latexmk' }, presets.latex.cmd) end) @@ -59,6 +71,10 @@ describe('presets', function() assert.is_table(clean) assert.are.same({ 'latexmk', '-c', '/tmp/document.tex' }, clean) end) + + it('has open enabled', function() + assert.are.same({ 'xdg-open' }, presets.latex.open) + end) end) describe('markdown', function() @@ -69,20 +85,84 @@ describe('presets', function() ft = 'markdown', } + it('has ft', function() + assert.are.equal('markdown', presets.markdown.ft) + end) + it('has cmd', function() assert.are.same({ 'pandoc' }, presets.markdown.cmd) end) - it('returns args with file and output flag', function() + it('returns args with standalone and embed-resources flags', function() local args = presets.markdown.args(md_ctx) assert.is_table(args) - assert.are.same({ '/tmp/document.md', '-o', '/tmp/document.pdf' }, args) + assert.are.same( + { '/tmp/document.md', '-s', '--embed-resources', '-o', '/tmp/document.html' }, + args + ) end) - it('returns pdf output path', function() + it('returns html output path', function() local output = presets.markdown.output(md_ctx) assert.is_string(output) - assert.are.equal('/tmp/document.pdf', output) + assert.are.equal('/tmp/document.html', output) + end) + + it('returns clean command', function() + local clean = presets.markdown.clean(md_ctx) + assert.is_table(clean) + assert.are.same({ 'rm', '-f', '/tmp/document.html' }, clean) + end) + + it('has open enabled', function() + assert.are.same({ 'xdg-open' }, presets.markdown.open) + end) + end) + + describe('github', function() + local md_ctx = { + bufnr = 1, + file = '/tmp/document.md', + root = '/tmp', + ft = 'markdown', + } + + it('has ft', function() + assert.are.equal('markdown', presets.github.ft) + end) + + it('has cmd', function() + assert.are.same({ 'pandoc' }, presets.github.cmd) + end) + + it('returns args with standalone, embed-resources, and css flags', function() + local args = presets.github.args(md_ctx) + assert.is_table(args) + assert.are.same({ + '/tmp/document.md', + '-s', + '--embed-resources', + '--css', + 'https://cdn.jsdelivr.net/gh/pixelbrackets/gfm-stylesheet@master/github.css', + '-o', + '/tmp/document.html', + }, args) + end) + + it('returns html output path', function() + local output = presets.github.output(md_ctx) + assert.is_string(output) + assert.are.equal('/tmp/document.html', output) + end) + + it('returns clean command', function() + local clean = presets.github.clean(md_ctx) + assert.is_table(clean) + assert.are.same({ 'rm', '-f', '/tmp/document.html' }, clean) + end) + + it('has open enabled', function() + assert.are.same({ 'xdg-open' }, presets.github.open) end) end) end)