diff --git a/README.md b/README.md index 3bbb203..80a2650 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Typst, Markdown, etc.)—diagnostics included. - Async compilation via `vim.system()` - Built-in presets for Typst, LaTeX (latexmk, pdflatex, tectonic), Markdown, - GitHub-flavored Markdown, AsciiDoc, and Quarto + GitHub-flavored Markdown, AsciiDoc, PlantUML, Mermaid, and Quarto - Compiler errors via `vim.diagnostic` or quickfix - Previewer auto-close on buffer deletion @@ -49,7 +49,7 @@ luarocks install preview.nvim **Q: How do I define a custom provider?** ```lua -require('preview').setup({ +vim.g.preview = { rst = { cmd = { 'rst2html' }, args = function(ctx) @@ -59,15 +59,15 @@ require('preview').setup({ return ctx.file:gsub('%.rst$', '.html') end, }, -}) +} ``` **Q: How do I override a preset?** ```lua -require('preview').setup({ +vim.g.preview = { typst = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } }, -}) +} ``` **Q: How do I automatically open the output file?** @@ -77,7 +77,7 @@ open the output with `vim.ui.open()` after the first successful compilation in toggle/watch mode. For a specific application, pass a command table: ```lua -require('preview').setup({ +vim.g.preview = { typst = { open = { 'sioyek', '--new-instance' } }, -}) +} ``` diff --git a/doc/preview.nvim.txt b/doc/preview.nvim.txt deleted file mode 100644 index c64db62..0000000 --- a/doc/preview.nvim.txt +++ /dev/null @@ -1,285 +0,0 @@ -*preview.nvim.txt* Async document compilation for Neovim - -Author: Barrett Ruth -License: MIT - -============================================================================== -INTRODUCTION *preview.nvim* - -preview.nvim is an extensible framework for compiling documents asynchronously -in Neovim. It provides a unified interface for any compilation workflow — -LaTeX, Typst, Markdown, or anything else with a CLI compiler. - -The plugin ships with opt-in presets for common tools (Typst, LaTeX, Pandoc, -AsciiDoc, Quarto) and supports fully custom providers. -See |preview.nvim-presets|. - -============================================================================== -REQUIREMENTS *preview.nvim-requirements* - -- Neovim >= 0.11.0 -- A compiler binary for each configured provider (e.g. `typst`, `latexmk`) - -============================================================================== -SETUP *preview.nvim-setup* - -With lazy.nvim, set |vim.g.preview| in `init` so configuration is applied -before the plugin loads: >lua - { - 'barrettruth/preview.nvim', - init = function() - vim.g.preview = { typst = true, latex = true } - end, - } -< -Alternatively, call |preview.setup()| directly in a `config` function or -anywhere the plugin is already loaded. - -============================================================================== -CONFIGURATION *preview.nvim-configuration* - -Configure via `require('preview').setup()`. - - *preview.setup()* -setup({opts?}) - - `opts` is a table where keys are preset names or filetypes. For each - key `k` with value `v` (excluding `debug`): - - - 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:~ - - `debug` boolean|string Enable debug logging. A string value - is treated as a log file path. - Default: `false` - - *preview.ProviderConfig* -Provider fields:~ - - `cmd` string[] The compiler command (required). - - `args` string[]|function Additional arguments. If a function, - receives a |preview.Context| and returns - a string[]. - - `cwd` string|function Working directory. If a function, - receives a |preview.Context|. Default: - git root or file directory. - - `env` table Environment variables. - - `output` string|function Output file path. If a function, - receives a |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|. - - `open` boolean|string[] Open the output file after the first - successful compilation in toggle/watch - mode. `true` uses |vim.ui.open()|. A - string[] is run as a command with the - output path appended. When a string[] - is used the viewer process is tracked - and sent SIGTERM when the buffer is - deleted. `true` and single-instance - apps (e.g. Chrome) do not support - auto-close. - - `reload` boolean|string[]|function - Reload the output after recompilation. - `true` uses a built-in SSE server for - HTML files. A string[] is run as a - command. If a function, receives a - |preview.Context| and returns a - string[]. - - `detach` boolean When `true`, the viewer process opened - via a string[] `open` command is not - sent SIGTERM when the buffer is deleted. - Has no effect when `open` is `true`. - Default: `false`. - - *preview.Context* -Context fields:~ - - `bufnr` integer Buffer number. - `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 - require('preview').setup({ typst = true, latex = true, github = true }) -< - -Example overriding a preset field:~ ->lua - require('preview').setup({ - typst = { open = { 'sioyek', '--new-instance' } }, - }) -< - -Example overriding the output path (e.g. latexmk `$out_dir`):~ ->lua - require('preview').setup({ - latex = { - output = function(ctx) - return 'build/' .. vim.fn.fnamemodify(ctx.file, ':t:r') .. '.pdf' - end, - }, - }) -< - -Example with a fully custom provider (key is not a preset name):~ ->lua - require('preview').setup({ - rst = { - cmd = { 'rst2html' }, - args = function(ctx) - return { ctx.file } - end, - output = function(ctx) - return ctx.file:gsub('%.rst$', '.html') - end, - }, - }) -< - -============================================================================== -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.pdflatex` pdflatex → PDF (single pass, no latexmk) - `presets.tectonic` tectonic → PDF (Rust-based LaTeX engine) - `presets.markdown` pandoc → HTML (standalone, embedded) - `presets.github` pandoc → HTML (GitHub-styled, `-f gfm` input) - `presets.asciidoctor` asciidoctor → HTML (AsciiDoc with SSE reload) - `presets.quarto` quarto render → HTML (scientific publishing) - -Enable presets with `preset_name = true`: ->lua - require('preview').setup({ typst = true, latex = true, github = true }) -< - -Override individual fields by passing a table instead of `true`: ->lua - require('preview').setup({ - typst = { env = { TYPST_FONT_PATHS = '/usr/share/fonts' } }, - }) -< - -============================================================================== -COMMANDS *preview.nvim-commands* - -:Preview [subcommand] *:Preview* - - Subcommands:~ - - `toggle` Toggle auto-compile on save (default if omitted). - `compile` One-shot compile of the current buffer. - `clean` Run the provider's clean command. - `open` Open the last compiled output without recompiling. - `status` Echo compilation status (idle, compiling, watching). - -============================================================================== -API *preview.nvim-api* - -preview.toggle({bufnr?}) *preview.toggle()* - Toggle auto-compile for the buffer. When enabled, the buffer is - immediately compiled and automatically recompiled on each save - (`BufWritePost`). Call again to stop. - -preview.compile({bufnr?}) *preview.compile()* - One-shot compile the document in the given buffer (default: current). - -preview.stop({bufnr?}) *preview.stop()* - Kill the active compilation process for the buffer. Programmatic - escape hatch — not exposed as a subcommand. - -preview.clean({bufnr?}) *preview.clean()* - Run the provider's clean command for the buffer. - -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:~ - - `compiling` boolean Whether compilation is active. - `watching` boolean Whether auto-compile is active. - `provider` string? Name of the active provider. - `output_file` string? Path to the output file. - -preview.get_config() *preview.get_config()* - Returns the resolved |preview.Config|. - -============================================================================== -EVENTS *preview.nvim-events* - -preview.nvim fires User autocmds with structured data: - -`PreviewCompileStarted` Compilation began. - data: `{ bufnr, provider }` - -`PreviewCompileSuccess` Compilation succeeded (exit code 0). - data: `{ bufnr, provider, output }` - -`PreviewCompileFailed` Compilation failed (non-zero exit). - data: `{ bufnr, provider, code, stderr }` - -Example:~ ->lua - vim.api.nvim_create_autocmd('User', { - pattern = 'PreviewCompileSuccess', - callback = function(args) - local data = args.data - vim.notify('Compiled ' .. data.output .. ' with ' .. data.provider) - end, - }) -< - -============================================================================== -HEALTH *preview.nvim-health* - -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 - -============================================================================== - vim:tw=78:ts=8:ft=help:norl: diff --git a/doc/preview.txt b/doc/preview.txt new file mode 100644 index 0000000..383a8f5 --- /dev/null +++ b/doc/preview.txt @@ -0,0 +1,276 @@ +*preview.txt* Async document compilation for Neovim + +Author: Barrett Ruth +License: MIT + +============================================================================== +INTRODUCTION *preview.nvim* + +preview.nvim is an extensible framework for compiling documents asynchronously +in Neovim. It provides a unified interface for any compilation workflow — +LaTeX, Typst, Markdown, or anything else with a CLI compiler. + +The plugin ships with opt-in presets for common tools (Typst, LaTeX, Pandoc, +AsciiDoc, PlantUML, Mermaid, Quarto) and supports fully custom providers. +See |preview-presets|. + +============================================================================== +CONTENTS *preview-contents* + + 1. Introduction ............................................. |preview.nvim| + 2. Requirements ..................................... |preview-requirements| + 3. Install ............................................... |preview-install| + 4. Configuration ........................................... |preview-config| + 5. Presets ............................................... |preview-presets| + 6. Commands ............................................. |preview-commands| + 7. Lua API ................................................... |preview-api| + 8. Events ............................................... |preview-events| + 9. Health ............................................... |preview-health| + +============================================================================== +REQUIREMENTS *preview-requirements* + +- Neovim >= 0.11.0 +- A compiler binary for each configured provider (e.g. `typst`, `latexmk`) + +============================================================================== +INSTALL *preview-install* + +Install with lazy.nvim: >lua + { 'barrettruth/preview.nvim' } +< + +No `setup()` call is needed. The plugin loads automatically when +|vim.g.preview| is set. See |preview-config|. + +============================================================================== +CONFIGURATION *preview-config* + +Configure by setting |vim.g.preview| to a table where keys are preset names +or filetypes. For each key `k` with value `v` (excluding `debug`): + + - 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-presets| for available preset names. + + *preview.ProviderConfig* +Provider fields: ~ + + {cmd} (string[]) Compiler command (required). + + {args} (string[]|function) Additional arguments. If a function, + receives a |preview.Context| and + returns a string[]. + + {cwd} (string|function) Working directory. If a function, + receives a |preview.Context|. + Default: git root or file directory. + + {env} (table) Environment variables. + + {output} (string|function) Output file path. If a function, + receives a |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|. + + {open} (boolean|string[]) Open the output file after the first + successful compilation in toggle/watch + mode. `true` uses |vim.ui.open()|. A + string[] is run as a command with the + output path appended. When a string[] + is used the viewer process is tracked + and sent SIGTERM when the buffer is + deleted. `true` and single-instance + apps (e.g. Chrome) do not support + auto-close. + + {reload} (boolean|string[]|function) + Reload the output after recompilation. + `true` uses a built-in SSE server for + HTML files. A string[] is run as a + command. If a function, receives a + |preview.Context| and returns a + string[]. + + {detach} (boolean) When `true`, the viewer process opened + via a string[] `open` command is not + sent SIGTERM when the buffer is + deleted. Has no effect when `open` is + `true`. Default: `false`. + + *preview.Context* +Context fields: ~ + + {bufnr} (integer) Buffer number. + {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). + +Global options: ~ + + {debug} (boolean|string) Enable debug logging. A string value is treated + as a log file path. Default: `false`. + +Example enabling presets: >lua + vim.g.preview = { typst = true, latex = true, github = true } +< + +Example overriding a preset field: >lua + vim.g.preview = { + typst = { open = { 'sioyek', '--new-instance' } }, + } +< + +Example overriding the output path (e.g. latexmk `$out_dir`): >lua + vim.g.preview = { + latex = { + output = function(ctx) + return 'build/' .. vim.fn.fnamemodify(ctx.file, ':t:r') .. '.pdf' + end, + }, + } +< + +Example with a fully custom provider (key is not a preset name): >lua + vim.g.preview = { + rst = { + cmd = { 'rst2html' }, + args = function(ctx) + return { ctx.file } + end, + output = function(ctx) + return ctx.file:gsub('%.rst$', '.html') + end, + }, + } +< + +============================================================================== +PRESETS *preview-presets* + +Built-in provider configurations. Enable with `preset_name = true` or +override individual fields by passing a table instead: >lua + vim.g.preview = { typst = true, latex = true, github = true } +< + + `typst` typst compile → PDF + `latex` latexmk -pdf → PDF (with clean support) + `pdflatex` pdflatex → PDF (single pass, no latexmk) + `tectonic` tectonic → PDF (Rust-based LaTeX engine) + `markdown` pandoc → HTML (standalone, embedded) + `github` pandoc → HTML (GitHub-styled, `-f gfm` input) + `asciidoctor` asciidoctor → HTML (AsciiDoc with SSE reload) + `plantuml` plantuml → SVG (UML diagrams, `.puml`) + `mermaid` mmdc → SVG (Mermaid diagrams, `.mmd`) + `quarto` quarto render → HTML (scientific publishing) + +============================================================================== +COMMANDS *preview-commands* + +:Preview [subcommand] *:Preview* + + Subcommands: ~ + + `toggle` Toggle auto-compile on save (default if omitted). + `compile` One-shot compile of the current buffer. + `clean` Run the provider's clean command. + `open` Open the last compiled output without recompiling. + `status` Echo compilation status (idle, compiling, watching). + +============================================================================== +LUA API *preview-api* + +preview.toggle({bufnr?}) *preview.toggle()* + Toggle auto-compile for the buffer. When enabled, the buffer is + immediately compiled and automatically recompiled on each save + (`BufWritePost`). Call again to stop. + +preview.compile({bufnr?}) *preview.compile()* + One-shot compile the document in the given buffer (default: current). + +preview.stop({bufnr?}) *preview.stop()* + Kill the active compilation process for the buffer. Programmatic + escape hatch — not exposed as a subcommand. + +preview.clean({bufnr?}) *preview.clean()* + Run the provider's clean command for the buffer. + +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.get_config() *preview.get_config()* + Returns the resolved |preview.Config|. + + *preview.Status* +Status fields: ~ + + {compiling} (boolean) Whether compilation is active. + {watching} (boolean) Whether auto-compile is active. + {provider} (string?) Name of the active provider. + {output_file} (string?) Path to the output file. + +============================================================================== +EVENTS *preview-events* + +preview.nvim fires User autocmds with structured data: + +`PreviewCompileStarted` Compilation began. + data: `{ bufnr, provider }` + +`PreviewCompileSuccess` Compilation succeeded (exit code 0). + data: `{ bufnr, provider, output }` + +`PreviewCompileFailed` Compilation failed (non-zero exit). + data: `{ bufnr, provider, code, stderr }` + +Example: >lua + vim.api.nvim_create_autocmd('User', { + pattern = 'PreviewCompileSuccess', + callback = function(args) + local data = args.data + vim.notify('Compiled ' .. data.output .. ' with ' .. data.provider) + end, + }) +< + +============================================================================== +HEALTH *preview-health* + +Run |:checkhealth| preview to verify your setup: >vim + :checkhealth preview +< + +Checks: ~ +- Neovim version >= 0.11.0 +- Each configured provider's binary is executable +- Each configured provider's opener binary (if any) is executable + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: diff --git a/flake.nix b/flake.nix index 91a0ab2..636f4d0 100644 --- a/flake.nix +++ b/flake.nix @@ -19,9 +19,9 @@ { formatter = forEachSystem (pkgs: pkgs.nixfmt-tree); - devShells = forEachSystem (pkgs: { - default = pkgs.mkShell { - packages = [ + devShells = forEachSystem (pkgs: + let + devTools = [ (pkgs.luajit.withPackages ( ps: with ps; [ busted @@ -33,7 +33,23 @@ pkgs.selene pkgs.lua-language-server ]; - }; - }); + in + { + default = pkgs.mkShell { + packages = devTools; + }; + presets = pkgs.mkShell { + packages = devTools ++ [ + pkgs.typst + pkgs.texliveMedium + pkgs.tectonic + pkgs.pandoc + pkgs.asciidoctor + pkgs.quarto + pkgs.plantuml + pkgs.mermaid-cli + ]; + }; + }); }; } diff --git a/lua/preview/init.lua b/lua/preview/init.lua index 322b893..7cb982b 100644 --- a/lua/preview/init.lua +++ b/lua/preview/init.lua @@ -105,6 +105,12 @@ function M.setup(opts) vim.validate(prefix .. '.detach', provider.detach, 'boolean', true) end + if providers['plantuml'] then + vim.filetype.add({ + extension = { puml = 'plantuml', pu = 'plantuml' }, + }) + end + config = vim.tbl_deep_extend('force', default_config, { debug = debug, providers = providers, diff --git a/lua/preview/presets.lua b/lua/preview/presets.lua index 3591a21..1b5333e 100644 --- a/lua/preview/presets.lua +++ b/lua/preview/presets.lua @@ -271,6 +271,68 @@ M.asciidoctor = { reload = true, } +---@type preview.ProviderConfig +M.plantuml = { + ft = 'plantuml', + cmd = { 'plantuml' }, + args = function(ctx) + return { '-tsvg', ctx.file } + end, + output = function(ctx) + return (ctx.file:gsub('%.puml$', '.svg')) + end, + error_parser = function(output) + local diagnostics = {} + for line in output:gmatch('[^\r\n]+') do + local lnum = line:match('^Error line (%d+) in file:') + if lnum then + table.insert(diagnostics, { + lnum = tonumber(lnum) - 1, + col = 0, + message = line, + severity = vim.diagnostic.severity.ERROR, + }) + end + end + return diagnostics + end, + clean = function(ctx) + return { 'rm', '-f', (ctx.file:gsub('%.puml$', '.svg')) } + end, + open = true, +} + +---@type preview.ProviderConfig +M.mermaid = { + ft = 'mermaid', + cmd = { 'mmdc' }, + args = function(ctx) + return { '-i', ctx.file, '-o', ctx.output } + end, + output = function(ctx) + return (ctx.file:gsub('%.mmd$', '.svg')) + end, + error_parser = function(output) + local diagnostics = {} + for line in output:gmatch('[^\r\n]+') do + local lnum = line:match('^%s*Parse error on line (%d+)') + if lnum then + table.insert(diagnostics, { + lnum = tonumber(lnum) - 1, + col = 0, + message = line, + severity = vim.diagnostic.severity.ERROR, + }) + end + end + return diagnostics + end, + clean = function(ctx) + return { 'rm', '-f', (ctx.file:gsub('%.mmd$', '.svg')) } + end, + open = true, +} + ---@type preview.ProviderConfig M.quarto = { ft = 'quarto',