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 *pdfport-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-intro*

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 *pdfport-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 *pdfport-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 *pdfport-config*

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.pdf invokes 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's lib.nvim.progress module.

                               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 via
                               spawn_capture, which exposes no killable
                               handle, so "float"/"kit"'s abort prompt would
                               close the indicator while the process kept
                               running. timeout_ms is 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 *pdfport-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-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 *pdfport-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 *pdfport-producers*

  img2pdf     Requires pip install img2pdf. Image -> PDF, lossless
              (embeds JPEG/PNG data unchanged instead of recompressing it).
  magick      Requires ImageMagick (magick on PATH). Image -> PDF,
              multi-image -> multi-page natively; recompresses.
  pandoc      Requires pandoc plus one PDF engine. Markdown/text -> PDF.
              Engine auto-detected: tectonic -> typst -> xelatex -> lualatex
              -> pdflatex, or pin one via pdf_engine.
  weasyprint  Requires pip 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 (soffice on PATH). Office
              (docx/odt/xlsx/pptx) -> PDF, one call covers all four.
  qpdf        Requires qpdf on PATH. PDF + PDF -> PDF (merge), first
              choice: exact, no page-content re-encoding.
  pdftk       Requires pdftk on 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 *pdfport-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 *pdfport-filetrees*

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 *pdfport-fuzzyfinders*

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 *pdfport-whichkey*

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 *pdfport-health*

: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