pdfport.nvim · View & render · vimdoc
:help pdfport
PDF extraction and display for Neovim
doc/pdfport.txt — rendered from the plugin's own vimdoc
*pdfport.txt* PDF extraction and display for Neovim *pdfport.nvim*
CONTENTS
1. Introduction ........................ |pdfport-intro| 2. Requirements ........................ |pdfport-requirements| 3. Setup ............................... |pdfport-setup| 4. Configuration ....................... |pdfport-config| 5. Commands ............................ |pdfport-commands| 6. Lua API ............................. |pdfport-api| 7. Backends ............................ |pdfport-backends| 8. Producers ............................ |pdfport-producers| 9. Renderers ........................... |pdfport-renderers| 10. File-tree integrations .............. |pdfport-filetrees| 11. Fuzzy-finder integrations ........... |pdfport-fuzzyfinders| 12. Which-key ............................ |pdfport-whichkey| 13. Health check ........................ |pdfport-health|
1. INTRODUCTION
pdfport.nvim extracts text from PDF files and displays it inside Neovim. It uses a pluggable backend/renderer architecture so you can choose the extraction tool that suits your workflow — from the fast built-in pdftotext to AI-powered cloud and local backends.
2. REQUIREMENTS
- Neovim >= 0.9 - lib.nvim (github.com/StefanBartl/lib.nvim) -- required: the :PdfPort command is built on lib.nvim.bindings.usercmd.composer - At least one extraction backend installed (see |pdfport-backends|)
3. SETUP
With lazy.nvim:
{
"StefanBartl/pdfport.nvim",
dependencies = { "StefanBartl/lib.nvim" },
cmd = { "PdfPort" },
opts = { default_backend = "auto" },
}
Manual:
require("pdfport").setup({ default_backend = "auto" })
4. CONFIGURATION
Pass a table to |pdfport.setup()|. All keys are optional. default_backend string "auto" or a backend id (default: "auto") fallback_chain string[] Backend ids tried in order for "auto" extract_opts table max_pages integer? Pages to extract (nil = all; ollama and tesseract process page 1 only when neither pages nor max_pages is given) timeout_ms integer? Per-backend timeout in ms (default: unset, each backend applies its own: 30 s / 60 s / 120 s) cache boolean Cache successful extractions across sessions, invalidated by the source file's mtime (default: true) render_opts table mode string "buffer"|"float"|"terminal"|"system" split string "vsplit"|"split"|"tab"|"current" focus boolean Focus the output window (default: true) render_opts.terminal_dpi integer pdftoppm DPI, terminal mode (default: 216) render_opts.terminal_size_ratio table { width, height } fraction of editor size used by the terminal image (default: 0.9/0.8) create_opts table Options for pdfport.create() (see |pdfport-producers|) page_size string Default: "A4" margin string Default: "20mm" dpi integer Image path only (default: 300) fit string "contain"|"fill"|"native" (default: "contain") timeout_ms integer Per-producer timeout in ms (default: 60000) create_chain table Per input-kind producer fallback chain (default: image = { "img2pdf", "magick" }, markdown/text = { "pandoc" }, html = { "weasyprint", "chromium" }, office = { "soffice" }, pdf = { "qpdf", "pdftk", "ghostscript" } -- the merge chain, used by pdfport.merge()) pdf_engine string pandoc --pdf-engine preference (default: "auto") "auto"|"tectonic"|"typst"|"xelatex"|"lualatex"|"pdflatex" claude_api_key string? Overrides $ANTHROPIC_API_KEY gemini_api_key string? Overrides $GEMINI_API_KEY ollama_host string Ollama API URL (default: localhost:11434) ollama_model string Ollama model name (default: "llava") auto_open_on_read boolean Opt-in BufReadCmd *.pdf::e file.pdfinvokes the mode picker instead of loading raw bytes (default: false) progress_style string Indicator while a backend extracts (default: "auto") "auto"|"notify"|"statusline"|"fidget"|"float"|"kit" Extraction is asynchronous, and the OCR/AI backends (marker, docling, ollama, tesseract) can run for minutes on a large PDF — until now with nothing on screen to say so. Provided by lib.nvim'slib.nvim.progressmodule. Wired in core/dispatcher.lua rather than per backend, so all eight — and any backend you register yourself — report progress for free. NOT cancellable: backends spawn viaspawn_capture, which exposes no killable handle, so "float"/"kit"'s abort prompt would close the indicator while the process kept running.timeout_msis the only real bound. Prefer "notify" or "statusline" here. A cache hit starts no indicator (it returns immediately), and the ~150ms delay guard means a fast pdftotext run never flashes any UI. debug boolean Emit debug notifications (default: false) Backends are registered lazily: setup() only registers lightweight proxies for the builtins; the real backend module is only require()d the first time the resolver actually calls available()/extract() on it. See lua/pdfport/config/DEFAULTS.lua for the authoritative defaults.
5. COMMANDS
One command, :PdfPort [subcommand] [path] (built via
lib.nvim.bindings.usercmd.composer, with <Tab> completion -- .pdf files prioritized,
<cfile> suggested when completing with no input yet).
*:PdfPort*
:PdfPort [path]
Open a PDF with an interactive mode/backend picker.
Path falls back to <cfile> and then the current buffer name.
:PdfPort text [path] *:PdfPort-text*
Extract text to a scratch buffer using the auto backend chain.
:PdfPort float [path] *:PdfPort-float*
Extract text and display in a centered floating window.
Prompts for a page range first (e.g. "1-3,5"; blank = all pages;
<Esc> cancels without opening anything).
:PdfPort system [path] *:PdfPort-system*
Open the PDF with the operating system's default application.
:PdfPort terminal [path] *:PdfPort-terminal*
Rasterize and display the first page as a terminal image.
Requires pdftoppm and one of: chafa, kitten, imgcat.
Prompts for a page range first (blank = page 1; <Esc> cancels).
:PdfPort backends *:PdfPort-backends*
List every registered backend with its live available() status in a
floating scratch window.
:PdfPort create [path] *:PdfPort-create*
Create a PDF from an image/markdown/text/html/office file. Path falls
back to <cfile> and then the current buffer name; output defaults to
the input's stem + ".pdf" next to it. See |pdfport-producers|.
:PdfPort merge {output} {input1} {input2} ... *:PdfPort-merge*
Merge two or more existing PDFs into one, e.g.
":PdfPort merge out.pdf a.pdf b.pdf c.pdf". See |pdfport.merge()|.
:PdfPort producers *:PdfPort-producers*
List every registered creation producer with its live available()
status in a floating scratch window.
:PdfPort health *:PdfPort-health*
Shortcut for |:checkhealth| pdfport.
6. LUA API
*pdfport.setup()* require("pdfport").setup({opts}) Initialize the plugin. Registers backends, renderers, and user commands. Safe to call multiple times; later calls override earlier configuration. *pdfport.open()* require("pdfport").open({opts}) Extract text and render it. {opts} fields: path string (required) Absolute path to the PDF mode string "buffer"|"float"|"terminal"|"system" backend_id string? Specific backend; nil = use fallback chain split string? "vsplit"|"split"|"tab"|"current" focus boolean? Focus the output window *pdfport.extract()* require("pdfport").extract({opts}) Extract text only, no rendering. {opts} fields: path string (required) Absolute path to the PDF max_pages integer? Page limit backend_id string? Specific backend __callback function Called as callback(result: PdfPort.Result) PdfPort.Result fields: status "ok"|"error" text string? format "plain"|"markdown" backend string pages_processed integer? error string? *pdfport.register_backend()* require("pdfport").register_backend({backend}) Register a custom extraction backend. {backend} fields: id string Unique identifier name string Human-readable name available function Returns true when the backend is usable extract function function(path, opts) — must call opts.__callback *pdfport.create()* require("pdfport").create({opts}) Create a PDF from something else — the reverse of open()/extract(). {opts} fields (exactly one of inputs/text/bufnr): inputs string[] File paths, in page order text string? Content directly; requires from + output bufnr integer? Buffer content; requires from + output output string? Output path; default with inputs: first input's stem + .pdf. Required with text/bufnr. from string? "image"|"markdown"|"html"|"text"|"office"|"pdf"; guessed from the first input's extension when using inputs; required with text/bufnr producer_id string? Specific producer; nil = use create_chain on_conflict string? "overwrite"|"suffix"|"error" (default: "overwrite") opts table? page_size/margin/dpi/fit/title/toc/template/timeout_ms __callback function? Called as callback(result: PdfPort.CreateResult); default notifies success/failure text/bufnr inputs are materialized to a temp file via util/tmpfile.lua first and cleaned up again once the result callback has fired — producers only ever see a real path either way. PdfPort.CreateResult fields: status "ok"|"error"|"partial" path string? producer string pages integer? error string? *pdfport.can_create()* require("pdfport").can_create(kind) Returns true if a producer is available for input kind {kind} ("image"|"markdown"|"html"|"text"|"office"|"pdf"). *pdfport.register_producer()* require("pdfport").register_producer({producer}) Register a custom creation producer. {producer} fields: id string Unique identifier name string Human-readable name accepts string[] Input kinds this producer handles available function Returns true when the producer is usable create function function(req) — must call req.__callback *pdfport.merge()* require("pdfport").merge({opts}) Merge two or more existing PDFs into one. Thin wrapper over create() with the input kind fixed to "pdf" — reuses the create_chain.pdf fallback chain (qpdf -> pdftk -> ghostscript by default). {opts} fields: inputs string[] At least 2 PDF paths, in output order (required) output string Output path (required, no default) producer_id string? Specific producer; nil = use create_chain.pdf on_conflict string? "overwrite"|"suffix"|"error" (default: "overwrite") opts table? timeout_ms, etc. __callback function? Called as callback(result: PdfPort.CreateResult); default notifies success/failure
7. BACKENDS
pdftotext Requires pdftotext (poppler-utils). Plain text output.
pdfplumber Requires Python + pdfplumber. Plain text output.
marker Requires marker_single (pip install marker-pdf). Markdown.
docling Requires Python + docling (pip install docling). Markdown.
ollama Requires ai.nvim, ollama, pdftoppm, curl. Markdown.
tesseract Requires tesseract, pdftoppm. OCR fallback, plain text.
claude Requires ai.nvim, curl, ANTHROPIC_API_KEY, Neovim 0.10+
(vim.base64). Sends the PDF whole. Markdown.
gemini Requires ai.nvim, curl, GEMINI_API_KEY, Neovim 0.10+
(vim.base64). Sends the PDF whole, capped at 20 MB by
Google's inline-request limit. Markdown.
The fallback chain is tried in order until one succeeds. Successful extractions are
cached across restarts (see extract_opts.cache above); |:PdfPort-backends| lists every
registered backend's live availability.
8. PRODUCERS
img2pdf Requirespip install img2pdf. Image -> PDF, lossless (embeds JPEG/PNG data unchanged instead of recompressing it). magick Requires ImageMagick (magickon PATH). Image -> PDF, multi-image -> multi-page natively; recompresses. pandoc Requirespandocplus one PDF engine. Markdown/text -> PDF. Engine auto-detected: tectonic -> typst -> xelatex -> lualatex -> pdflatex, or pin one viapdf_engine. weasyprint Requirespip install weasyprint. HTML -> PDF, first choice (clean CSS Paged Media support). chromium Requires a Chromium-family browser on PATH (chromium, chromium-browser, google-chrome, chrome, or msedge). HTML -> PDF fallback via headless --print-to-pdf. soffice Requires LibreOffice (sofficeon PATH). Office (docx/odt/xlsx/pptx) -> PDF, one call covers all four. qpdf Requiresqpdfon PATH. PDF + PDF -> PDF (merge), first choice: exact, no page-content re-encoding. pdftk Requirespdftkon PATH. PDF + PDF -> PDF (merge fallback #2). ghostscript Requires gs/gswin64c/gswin32c on PATH. PDF + PDF -> PDF (merge fallback #3, last resort: recompresses). The per-input-kind chain (create_chain) is tried in order until one producer is both registered and available(); |:PdfPort-producers| lists every registered producer's live availability, including the "pdf"-kind merge producers used by |pdfport.merge()|.
9. RENDERERS
buffer Opens a scratch buffer (supports split/vsplit/tab). float Opens a centered floating window. Press q or <Esc> to close. system Opens the original PDF with the OS default application. terminal Rasterizes via pdftoppm and displays with chafa/kitty/imgcat.
10. FILE-TREE INTEGRATIONS
All integrations share the same default keymaps:
<leader>po — Mode picker (interactive) [normal mode]
<leader>pt — Extract to buffer (vsplit) [normal mode]
<leader>ps — Open with system application [normal mode]
<leader>pi — Terminal image preview [normal mode]
<leader>pb — Batch-open every PDF in the visual selection [visual mode]
Pass false for any action (open/open_text/open_system/open_terminal/open_batch) to
disable that keymap. See docs/BINDINGS.md in the repo for the full cheatsheet, and
section |pdfport-whichkey| below for which-key integration.
neo-tree:
local neo = require("pdfport.integrations.neotree")
-- Inside your neo-tree setup() opts:
commands = vim.tbl_extend("force", {}, neo.commands()),
filesystem.window.mappings = vim.tbl_extend("force", {}, neo.keymaps()),
nvim-tree:
require("pdfport.integrations.nvim_tree").setup()
netrw:
require("pdfport.integrations.netrw").setup()
oil.nvim:
require("pdfport.integrations.oil").setup()
Unified auto-detect:
require("pdfport.integrations").open_current({ split = "vsplit" })
11. FUZZY-FINDER INTEGRATIONS
Telescope — single picker:
require("telescope.builtin").find_files({
previewer = require("pdfport.integrations.telescope").previewer(),
})
Telescope — global hook:
require("telescope").setup({
defaults = {
preview = {
filetype_hook = require("pdfport.integrations.telescope").filetype_hook,
},
},
})
fzf-lua:
require("fzf-lua").files({
preview = require("pdfport.integrations.fzf").preview_fn({ max_pages = 3 }),
})
12. WHICH-KEY
If which-key.nvim (folke/which-key.nvim) is installed, every active file-tree keymap is automatically registered with a description under the <leader>p group (open_batch registered under Visual mode, everything else under Normal mode). No configuration required; this is a no-op when which-key is absent.
13. HEALTH CHECK
:checkhealth pdfport
Reports:
- Core module load status
- Backend availability (tool executables, Python modules, API keys)
- Producer availability (img2pdf, magick, pandoc, weasyprint, chromium,
soffice, and the merge producers qpdf/pdftk/ghostscript)
- Renderer availability (system open command, image renderer)
- Integration status (neo-tree, nvim-tree, oil.nvim, telescope, fzf-lua, which-key)
- Declared tools inventory (docs/install.json, via lib.nvim.deps)
- Live registry: all registered backends and producers with available() result