file:branding/logo/snapper_logo.png
A fast, format-aware semantic line break formatter. Reformats prose so each sentence occupies its own line, producing minimal and meaningful git diffs when collaborating on documents. When multiple authors collaborate on a paper using Git, traditional line wrapping at a fixed column width causes problems. A single word change can trigger a diff that spans an entire paragraph. By breaking at sentence boundaries instead, each edit affects only the sentence that changed.This convention, often called semantic line breaks, enjoys longstanding support from technical writers.
snapper is a deterministic formatter (UAX #29 plus abbreviation tables and optional nnsplit).
It is not admk/sembr (learned clause breaks) and not sembr/skills (agent rewrite instructions).
latexindent.pl covers LaTeX only; snapper is a standalone Rust binary for Org-mode, LaTeX, Markdown, RST, and plaintext.
snapper runs a three-stage pipeline:
- Parse
- Classify input into prose regions and structure regions
- Split
- Detect sentence boundaries in prose regions
- Emit
- Output each sentence on its own line
Math environments, tables, front matter, drawers, and non-source comments pass through unchanged.
Fenced and delimited source blocks are Region::Code: fence/open/close lines stay structure, non-comment code stays verbatim, and comment lines reflow at sentence boundaries when the language has a [code.<lang>] entry in .snapperrc.toml (snapper init seeds common languages).
Pass --format-code to also pipe each block body through an optional per-language formatter argv (missing binary, non-zero exit, and timeouts degrade to the reflowed body).
Sentence detection relies on Unicode UAX #29 segmentation with abbreviation-aware post-processing that avoids false breaks at titles (Dr., Prof.), references (Fig., Eq.), and Latin terms (e.g., i.e., et al.).
Org emphasis (*bold*, /italic/, _underline_, +strike+) is kept atomic so splits cannot open a pseudo-headline mid-span.
Markdown *em* / **strong** (CommonMark flanking) and GFM ~~strike~~ are kept atomic the same way.
cargo binstall snapper-fmtShell one-liner (Linux/macOS):
curl -LsSf https://github.com/TurtleTech-ehf/snapper/releases/latest/download/snapper-fmt-installer.sh | shHomebrew:
brew install TurtleTech-ehf/tap/snapper-fmtpip:
pip install snapper-fmtconda-forge:
conda install -c conda-forge snapper-fmtCompile from source:
cargo install snapper-fmtNix:
nix build github:TurtleTech-ehf/snapperThe crate is snapper-fmt on all registries.
Each install ships two CLI names for the same program: snapper and snapper-fmt.
Name collision: openSUSE snapper is a different project (Btrfs/LVM snapshots) that also installs a snapper binary.
On systems where that tool already owns /usr/bin/snapper, call this formatter as snapper-fmt, or put the TurtleTech install path ahead of the system path.
snapper paper.orgFormat in place:
snapper --in-place paper.orgPipe through stdin (for editor integration):
cat draft.org | snapper --native --format orgThe CLI uses pandoc (auto FFI, then CLI) when an FFI writer or pandoc on PATH is available.
Otherwise it keeps the native line parsers (no error).
Pass --native to force today’s parsers.
Pass --use-pandoc to require pandoc (error if missing).
Editors, wasm, and LSP stay native.
Check formatting without modifying (for CI):
snapper --check paper.org paper.tex notes.mdLimit line width (wrap long sentences at word boundaries):
snapper --max-width 80 paper.orgBreak after independent-clause punctuation (comma, semicolon, colon, em dash). With the default unlimited width this inserts a newline after every such mark that already has whitespace after it:
snapper --clause-breaks paper.orgWith --max-width set, overflowing sentences prefer those marks.
A one-clause sentence stays one line.
Preview changes as a unified diff before committing:
snapper --diff paper.orgCompare two versions at the sentence level (whitespace reflow produces zero diff):
snapper sdiff paper_v1.org paper_v2.orgWatch files and auto-reformat on save:
snapper watch '*.org' 'sections/*.tex'Initialize a project (generates config, pre-commit, gitattributes):
snapper initsnapper / snapper-fmt binaries include the MCP server (mcp is a default Cargo feature).
Start the stdio server:
snapper mcpAgents should call snapper MCP (format_text) or the snapper CLI instead of applying sembr.org / sembr/skills wrapping by hand.
format_text accepts clause_breaks, range (start / end, 1-indexed inclusive), and max_width (default 0).
From source with --no-default-features, rebuild with MCP:
cargo install snapper-fmt --features mcpTools: format_text, detect_format, check_formatting, split_sentences.
Configuration guide (org source in-tree): docs/orgmode/howto/mcp-integration.org; HTML docs: https://snapper.turtletech.us/docs/howto/mcp-integration/ .
| Format | Extensions | Structure / code handling |
|---|---|---|
| Org-mode | .org | Drawers, tables, keywords; #+BEGIN_SRC comment reflow |
| LaTeX | .tex, .latex | Preamble, math; minted and lstlisting comment reflow; Piton / \piton |
| Markdown | .md, .markdown | Front matter, HTML; fenced blocks comment reflow when configured |
| RST | .rst | Directives, literals; .. code-block:: comment reflow |
| Plaintext | .txt | (none; all text treated as prose). Unknown extensions are refused unless --format is set |
- repo: https://github.com/TurtleTech-ehf/snapper
rev: v0.11.6
hooks:
- id: snapper(with-eval-after-load 'apheleia
(push '(snapper . ("snapper" "--native" "--format" "org")) apheleia-formatters)
(push '(org-mode . snapper) apheleia-mode-alist))lazy.nvim (rocks support):
{
"TurtleTech-ehf/snapper",
ft = { "org", "tex", "markdown", "rst" },
config = function()
vim.opt.runtimepath:append(
vim.fn.stdpath("data") .. "/lazy/snapper/editors/nvim"
)
require("snapper").setup()
end,
}Or with rocks.nvim:
:Rocks install snapper.nvimPlug 'TurtleTech-ehf/snapper', { 'rtp': 'editors/vim' }This provides formatprg support for automatic formatting with the gq operator.
editors/obsidian and is available for development builds only.
Development preview; not published in AppSource.
The WebAssembly add-in source and sideloading instructions live in editors/word.
Auto-format on commit, transparent to collaborators:
git config filter.snapper.clean "snapper --native --format org"
git config filter.snapper.smudge catThen add to .gitattributes:
*.org filter=snapper
snapper ships a vale style package for editor hints.
Add to your .vale.ini:
StylesPath = /path/to/snapper/vale
[*.org]
BasedOnStyles = snapperFor precise CI checks, use snapper --check directly.
.snapperrc.toml in your project root:
extra_abbreviations = ["GROMACS", "LAMMPS", "DFT"]
ignore = ["*.bib", "*.cls"]
format = "org"
max_width = 0
clause_breaks = false
[latex]
verbatim_envs = ["Verbatim"]
structure_envs = ["algorithm", "comment"]
verbatim_commands = ["Verb"]Missing [latex] keys keep the built-in minted/lstlisting/verbatim/comment/Piton, equation/figure, and verb/lstinline/spverb/Verb/piton lists.
snapper walks up from the current directory to find it.
pixi run docbld- Clap 4 (derive)
- CLI argument parsing
- unicode-segmentation
- UAX #29 sentence boundaries
- regex
- Abbreviation and format pattern matching
- textwrap
- Optional line width limiting
- thiserror
- Typed error handling
cocogitto via cog to handle commit conventions.
Construct the readme via:
./scripts/org_to_md.sh readme_src.org README.md