Compare commits

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

9 commits

Author SHA1 Message Date
87f2ed4aa2
build: remove synctex test files 2026-03-05 22:37:52 -05:00
23bce48d27
docs: fix Okular inverse search instructions
Problem: Okular settings path was incomplete and didn't mention the
trigger keybinding.

Solution: Update to full path (Settings -> Configure Okular -> Editor)
and note that Shift+click triggers inverse search.
2026-03-05 22:37:48 -05:00
5c4fb10596
fix(nvim): simplify preview.nvim config 2026-03-05 22:36:35 -05:00
1e3d0fa577
fix(nvim): simplify preview.nvim config 2026-03-05 22:29:30 -05:00
f6199af390
build: add zathura and sioyek to nix dev shell 2026-03-05 21:59:45 -05:00
8dd8b533cc
docs: add SyncTeX section with viewer recipes
Problem: SyncTeX setup for forward/inverse search was undocumented,
forcing users to figure out viewer-specific CLI flags on their own.

Solution: Add `preview-synctex` vimdoc section with shared setup and
per-viewer recipes for Zathura, Sioyek, and Okular. Add FAQ entry
in README pointing to the new section.
2026-03-05 21:59:42 -05:00
Barrett Ruth
1fbc307bad
Update README to refine project description 2026-03-05 15:24:52 -05:00
Barrett Ruth
00caad18bf
refactor(presets): simplify mermaid error parser (#49)
* feat: add `mermaid` preset

Problem: no built-in support for compiling mermaid diagrams via `mmdc`.

Solution: add a `mermaid` preset that compiles `.mmd` files to SVG and
parses `Parse error on line N` diagnostics from stderr. Add
`mermaid-cli` to the nix dev shell.

* refactor(presets): simplify `mermaid` error parser

Problem: the inline `mermaid` error_parser looped over every line and
used the `Parse error on line N:` header as the message, losing the
useful `Expecting ..., got ...` token detail.

Solution: extract `parse_mermaid` alongside the other parse functions,
use a single `output:match` (mermaid's JISON parser stops at the first
error), and surface the `Expecting ..., got ...` line as the message.

* ci: format

* ci: format
2026-03-05 12:05:34 -05:00
Barrett Ruth
8c9847e321
build: split nix dev shell into default and presets (#48)
Problem: the single dev shell mixed dev tooling (linters, test runner)
with preset compiler tools, causing heavy rebuilds (e.g. Chromium for
`mermaid-cli`) for contributors who only need the dev tools.

Solution: extract dev tooling into a shared `devTools` list and expose
two shells — `default` for development and `presets` for running all
built-in preset compilers (`typst`, `texliveMedium`, `tectonic`,
`pandoc`, `asciidoctor`, `quarto`, `plantuml`, `mermaid-cli`).
2026-03-05 11:05:16 -05:00
4 changed files with 173 additions and 21 deletions

View file

@ -1,6 +1,6 @@
# preview.nvim
**Universal document previewer for Neovim**
**Universal previewer for Neovim**
An extensible framework for compiling and previewing _any_ documents (LaTeX,
Typst, Markdown, etc.)—diagnostics included.
@ -81,3 +81,8 @@ vim.g.preview = {
typst = { open = { 'sioyek', '--new-instance' } },
}
```
**Q: How do I set up SyncTeX (forward/inverse search)?**
See `:help preview-synctex` for full recipes covering Zathura, Sioyek, and
Okular.

View file

@ -26,6 +26,7 @@ CONTENTS *preview-contents*
7. Lua API ................................................... |preview-api|
8. Events ............................................... |preview-events|
9. Health ............................................... |preview-health|
10. SyncTeX ............................................. |preview-synctex|
==============================================================================
REQUIREMENTS *preview-requirements*
@ -272,5 +273,116 @@ Checks: ~
- Each configured provider's binary is executable
- Each configured provider's opener binary (if any) is executable
==============================================================================
SYNCTEX *preview-synctex*
SyncTeX enables bidirectional navigation between LaTeX source and the
compiled PDF. The `latex` preset compiles with `-synctex=1` by default.
Forward search (editor -> viewer) requires caching the output path.
Inverse search (viewer -> editor) requires a fixed Neovim server socket.
The following configs leverage the below basic setup: ~
>lua
vim.fn.serverstart('/tmp/nvim-preview.sock')
local synctex_pdf = {}
vim.api.nvim_create_autocmd('User', {
pattern = 'PreviewCompileSuccess',
callback = function(args)
synctex_pdf[args.data.bufnr] = args.data.output
end,
})
<
The recipes below bind `<leader>s` for forward search. To scroll the PDF
automatically on cursor movement, call the forward search function from a
|CursorMoved| or |CursorHold| autocmd instead.
Viewer-specific recipes: ~
*preview-synctex-zathura*
Zathura ~
Inverse search: Ctrl+click.
>lua
vim.keymap.set('n', '<leader>s', function()
local pdf = synctex_pdf[vim.api.nvim_get_current_buf()]
if pdf then
vim.fn.jobstart({
'zathura', '--synctex-forward',
vim.fn.line('.') .. ':0:' .. vim.fn.expand('%:p'), pdf,
})
end
end)
vim.g.preview = {
latex = {
open = {
'zathura',
'--synctex-editor-command',
'nvim --server /tmp/nvim-preview.sock'
.. [[ --remote-expr "execute('b +%{line} %{input}')"]],
},
},
}
<
*preview-synctex-sioyek*
Sioyek ~
Inverse search: right-click with synctex mode active.
Add to `~/.config/sioyek/prefs_user.config`: >
inverse_search_command nvim --server /tmp/nvim-preview.sock --remote-expr "execute('b +%2 %1')"
<
>lua
vim.keymap.set('n', '<leader>s', function()
local pdf = synctex_pdf[vim.api.nvim_get_current_buf()]
if pdf then
vim.fn.jobstart({
'sioyek',
'--instance-name', 'preview',
'--forward-search-file', vim.fn.expand('%:p'),
'--forward-search-line', tostring(vim.fn.line('.')),
pdf,
})
end
end)
vim.g.preview = {
latex = {
open = { 'sioyek', '--instance-name', 'preview' },
},
}
<
*preview-synctex-okular*
Okular ~
Inverse search (Shift+click): one-time GUI setup via
Settings -> Configure Okular -> Editor -> Custom Text Editor: >
nvim --server /tmp/nvim-preview.sock --remote-expr "execute('b +%l %f')"
<
>lua
vim.keymap.set('n', '<leader>s', function()
local pdf = synctex_pdf[vim.api.nvim_get_current_buf()]
if pdf then
vim.fn.jobstart({
'okular', '--unique',
('%s#src:%d:%s'):format(pdf, vim.fn.line('.'), vim.fn.expand('%:p')),
})
end
end)
vim.g.preview = {
latex = { open = { 'okular', '--unique' } },
}
<
==============================================================================
vim:tw=78:ts=8:ft=help:norl:

View file

@ -19,9 +19,10 @@
{
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
@ -32,10 +33,38 @@
pkgs.stylua
pkgs.selene
pkgs.lua-language-server
pkgs.plantuml
pkgs.mermaid-cli
];
};
});
okular-wrapped = pkgs.symlinkJoin {
name = "okular";
paths = [ pkgs.kdePackages.okular ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/okular \
--prefix XDG_DATA_DIRS : "${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}" \
--prefix XDG_DATA_DIRS : "${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}"
'';
};
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
pkgs.zathura
pkgs.sioyek
okular-wrapped
];
};
}
);
};
}

View file

@ -115,6 +115,24 @@ local function parse_asciidoctor(output)
return diagnostics
end
---@param output string
---@return preview.Diagnostic[]
local function parse_mermaid(output)
local lnum = output:match('Parse error on line (%d+)')
if not lnum then
return {}
end
local msg = output:match('(Expecting .+)') or 'parse error'
return {
{
lnum = tonumber(lnum) - 1,
col = 0,
message = msg,
severity = vim.diagnostic.severity.ERROR,
},
}
end
---@type preview.ProviderConfig
M.typst = {
ft = 'typst',
@ -313,19 +331,7 @@ M.mermaid = {
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
return parse_mermaid(output)
end,
clean = function(ctx)
return { 'rm', '-f', (ctx.file:gsub('%.mmd$', '.svg')) }