A from-scratch Rust clone of bat — cat with syntax highlighting, git diff markers, and a pager. Plus:
- Bundled Rhai grammar —
.rhaiscripts highlight out of the box, including template strings with${interpolation},#{}map literals,??/?.operators,::module paths, and a broad builtin list. - Interactive TUI mode (
-i) — vim-style navigation, line by line. - Vim-style relative line numbers — center distances on a cursor.
- Markdown rendering (
-m) — render Markdown likeglow, with amtoggle in interactive mode. - Tail / follow mode (
-f) —tail -fsemantics with syntax highlighting. - Live mode (
--live) — alt-screen TUI that re-renders on any file change. Same keys as-i; cursor + scroll position preserved across reloads. - Colorized
--examples— curated, copy-pasteable scenarios for every common flag. - Man page — install
man/batty.1to yourMANPATHforman batty. - TOML config —
~/.config/batty/config.toml. - Small release binary — ~2.8 MB with full grammar/theme bundles.
Targets macOS and Linux.
brew tap codedeviate/cli
brew install battyThe formula builds from source via cargo, so a Rust toolchain is pulled in as a build-time dependency and removed afterwards.
cargo install batty-catThe crate is published as batty-cat on crates.io because the batty and batty-cli names were already taken — the installed binary is still batty.
git clone https://github.com/codedeviate/batty.git && cd batty
cargo build --release
# Binary lands at target/release/battyMove it onto your $PATH however you like (e.g. cp target/release/batty ~/.local/bin/).
A man page lives at man/batty.1 in the repo. To install it locally:
# macOS (Homebrew default MANPATH)
cp man/batty.1 /usr/local/share/man/man1/
# Linux (typical)
sudo cp man/batty.1 /usr/local/share/man/man1/
sudo mandb # rebuild index if your distro cachesThen man batty works. You can also preview without installing: man ./man/batty.1.
Requires Rust 1.86 or newer.
batty src/main.rs # full decorations
batty -p src/main.rs # plain output
batty -i src/main.rs # interactive TUI
batty -m README.md # render Markdown (glow-style)
batty -f error.log # tail -f with highlighting
batty --line-range 10:30 file.rs # only lines 10–30
echo 'fn main() {}' | batty -l rust # stdin with language hint
batty --diff modified_file.rs # gutter diff markers
batty --list-languages # show all bundled languages
batty --list-themes # show all bundled themesbatty accepts the flags below. Many can also be set in the config file.
| Flag | Description |
|---|---|
<FILES>... |
One or more files. Use - for stdin (default when no files given). |
-l, --language <NAME> |
Override syntax detection. Use any name shown by --list-languages. |
--encoding <ENC> |
Input file encoding: auto (default), utf-8, iso-8859-1 (alias latin1). auto decodes as UTF-8 when valid and silently falls back to ISO-8859-1 otherwise; utf-8 is strict and errors on invalid byte sequences; iso-8859-1 always decodes byte-for-byte (each byte 0x00–0xFF → U+0000–U+00FF). Applies to file reads and stdin. |
| Flag | Description |
|---|---|
-p, --plain |
No decorations (header / grid / numbers / changes). Equivalent to --style=plain. |
-n, --number |
Show line numbers. Equivalent to --style=numbers. |
-d, --diff |
Show git diff markers in the gutter (+ / ~ / -). |
--diff-context <N> |
Lines of context for diff display. Default: 2. (Parsed but currently doesn't filter to changed regions; see Limitations.) |
-A, --show-all |
Show non-printable characters: → for tab, · for space, • for control chars. |
--style <SPEC> |
Comma-separated style components: full, plain, numbers, grid, header, rule, changes, snip. Default: full. |
--gutter / --no-gutter |
Force the left-side gutter (line numbers + diff markers + grid bar) on or off, regardless of --style. --no-gutter is a "cleaner reading" preset that's less aggressive than --plain (header / rule / snip stay on). --gutter cancels no-gutter = true from config. |
--line-range <RANGE> |
Show only the given range, e.g. 10:20, :15, 30:, 42. Rejects 0 and inverted ranges. |
-H, --highlight-line <N> |
Highlight specific line(s) with inverse video. Repeatable. The first one acts as the cursor reference for relative numbering. |
--tabs <N> |
Tab expansion width. Default: 4. |
--wrap <MODE> |
never / character / word / auto. Default: auto. auto wraps when stdout is a TTY (behaving as character) and behaves as never otherwise (so pipes / file redirection don't get continuation prefixes injected). character always breaks long lines at the terminal-width boundary with a continuation prefix that preserves the gutter (line numbers / cursor / change marker remain blank on continuation rows; the grid bar repeats). Wide CJK / emoji chars count their actual display width. word breaks at whitespace boundaries instead — best for prose. Words longer than the wrap width fall back to character-style breaks automatically, so output never overflows. --wrap=never emits each source line in one shot, letting the terminal soft-wrap (or truncate). Forced to never in interactive mode. |
--decorations <WHEN> |
always / auto / never. Default: auto. never collapses to plain output. |
| Flag | Description |
|---|---|
--theme <NAME> |
Color theme. Default: Monokai Extended. See --list-themes. |
--color <WHEN> |
always / auto / never. Default: auto. auto enables color when stdout is a TTY and NO_COLOR is unset. |
--line-numbers <STYLE> |
absolute (default) or relative. With relative, the cursor line shows its absolute number, others show distance. Falls back to absolute if no cursor is set. |
| Flag | Description |
|---|---|
-m, --markdown |
Render Markdown to terminal escapes instead of showing the raw source. Uses termimad under the hood. Works on any file, not just .md. |
--markdown-on-extension |
Render as Markdown only when the file extension is .md / .markdown / .mdown / .mkd. Lower priority than --markdown (which forces on for any file) and --no-markdown (which disables). Useful as a config default — set markdown-on-extension = true and .md files auto-render while source files stay raw. |
--no-markdown |
Disable Markdown rendering. Overrides markdown = true and markdown-on-extension = true in the config. |
When --markdown is on, the gutter shows the source-line number of each top-level block on its first rendered row, with continuation rows blank in the gutter (matching how raw view handles wrapped lines). The grid bar repeats on every row when --style includes grid. Use --no-gutter to strip the gutter and read flush against the left margin. Diff markers (changes) and the cursor glyph (▶) don't appear in markdown view — block-granular mapping doesn't make them meaningful, and the status bar covers position info in interactive mode. The header prints with the language label Markdown (rendered).
| Flag | Description |
|---|---|
-i, --interactive |
Enter the TUI. See Interactive mode for keys. |
--no-interactive |
Disable interactive mode. Overrides interactive = true in the config. |
--top-pad <N> |
Reserve N rows at the top of the screen. Default: 0. Use 2 in Warp to dodge its UI overlay. |
| Flag | Description |
|---|---|
--live |
Alt-screen TUI that re-renders the file on any content change. Same keybindings as -i (j/k, g/G, Ctrl-d/u, m, n, +/-, q). Cursor + scroll position preserved across reloads (clamped if the file shrinks). Single file only; no stdin. Mutually exclusive with --interactive and --follow. |
--no-live |
Disable live mode. Overrides live = true in the config. |
Detection: cheap mtime+len gate, then a byte compare to suppress touch / vim :w no-ops. Polled every 200 ms. The status bar shows [live], flashing [live · reloaded] for 1.5 s after each reload.
| Flag | Description |
|---|---|
-f, --follow |
tail -f semantics: render the last --tail-lines lines, then poll the file every 200 ms and render appended content as it arrives. Single file only; no stdin; bypasses the pager. Mutually exclusive with --interactive. Ctrl-C exits. |
--no-follow |
Disable follow mode. Overrides follow = true in the config. |
--tail-lines <N> |
Number of trailing lines to show on launch. Default: 10. |
Truncation / rotation: when the file shrinks (e.g. > error.log or logrotate), batty prints a one-line notice and re-renders the last --tail-lines of the new content.
| Flag | Description |
|---|---|
--paging <WHEN> |
always / auto / never. Default: auto. Uses $PAGER or less -RF. never also disables interactive mode — treat it as a global "flat output" override. |
| Flag | Description |
|---|---|
-L, --list-languages |
Print every supported language (one per line) and exit. |
--list-themes |
Print every bundled theme and exit. |
--examples |
Print colorized, copy-pasteable usage examples for every common flag and exit. Complements --help (terse) with curated scenarios. |
-h, --help |
Short help. |
--help |
Long help with full descriptions. |
-V, --version |
Print version. |
- All boolean flags use
overrides_withso config + CLI can both set a flag withoutcannot be used multiple timeserrors. --interactiveand--no-interactiveare mutual overrides — last occurrence wins.--paging=neverimplies--no-interactive.
~/.config/batty/config.toml (XDG-style on every platform, including macOS).
Top-level keys mirror CLI long flag names with hyphens preserved:
# ~/.config/batty/config.toml
theme = "Dracula"
tabs = 2
top-pad = 2
line-numbers = "relative"
interactive = true
markdown = false # true → render every file as markdown
markdown-on-extension = true # true → render only .md / .markdown files
no-gutter = false # true → hide line numbers / changes / grid by default
follow = false # set true to default to tail mode
live = false # set true to default to live mode (alt-screen autoreload)
tail-lines = 10
highlight-line = [10, 20]
encoding = "auto" # "utf-8" | "iso-8859-1" | "auto" (default)| TOML | argv |
|---|---|
key = "string" |
--key=string |
key = 42 |
--key=42 |
key = true |
--key |
key = false |
(omitted) |
key = [a, b] |
--key=a --key=b |
- Comments (
#) are allowed. - Malformed TOML logs a warning to stderr and proceeds without config.
- Unknown keys are forwarded to clap, which rejects them with a clear error — typos surface as
unrecognized argument.
Set this env var to override the default location:
BATTY_CONFIG_PATH=/path/to/other.toml batty foo.rs
BATTY_CONFIG_PATH=/dev/null batty foo.rs # opt out of any configbatty -i src/main.rsEnters raw mode in the alternate screen. A ▶ glyph in the gutter marks the cursor line; the bottom row is a status bar (file line N/M (abs|rel mode) vim-keys: ... q quit).
| Key | Action |
|---|---|
j / Down |
Cursor down one line |
k / Up |
Cursor up one line |
g / Home |
Jump to first line |
G / End |
Jump to last line |
Ctrl-d |
Half-page down |
Ctrl-u |
Half-page up |
PageDown |
Full page down |
PageUp |
Full page up |
m |
Toggle rendered Markdown view ↔ raw source. Active when the file has a .md / .markdown / .mdown / .mkd extension, or when --markdown was passed on launch. Status bar shows [md] while in rendered mode. The toggle preserves your scroll position: pressing m from raw view lands you on the corresponding block in the rendered output, and pressing m again returns to the source line of the block you were viewing. The status bar in rendered mode shows rendered N/M ↔ src K. |
n |
Toggle the gutter (line numbers + cursor glyph) on/off. Initial state follows --gutter / --no-gutter. Status bar shows no-gutter when off. |
+ / = |
Increase --top-pad by 1 row (live). Status bar shows pad=N when nonzero. |
- |
Decrease --top-pad by 1 row (saturates at 0). |
q / Esc / Ctrl-c |
Quit |
If your terminal hides the top of the alt-screen behind its own UI (Warp does this in some panes / after resizing), the live + / - bindings let you tune top-pad until content is visible — no need to exit, edit config, and retry. The value resets to whatever --top-pad (or top-pad in config) said next time you launch.
Restrictions:
- One file at a time — multiple files are rejected with an error.
- No stdin input — interactive mode requires a real file path.
- Pager is bypassed.
- Color is forced on (interactive mode in a TTY always wants color).
If the top of your screen is hidden behind a terminal-host overlay (e.g. Warp), pass --top-pad=2 (or whatever number works) — or set top-pad = 2 in your config.
| Variable | Effect |
|---|---|
BATTY_CONFIG_PATH |
Override the config file path. /dev/null disables config entirely. |
PAGER |
Pager binary (default less). |
LESS |
Pager arguments. batty sets LESS=-RF if unset before spawning the pager. |
NO_COLOR |
When set (any value), disables color output in --color=auto mode. |
# Render a README in your terminal
batty -m README.md
# Mix: render markdown AND open it interactively (m toggles to raw)
batty -m -i README.md
# Tail a log file with syntax highlighting
batty -f error.log
# Tail with the last 50 lines visible on launch
batty -f --tail-lines=50 error.log
# Highlight a Rhai script
batty path/to/script.rhai
# Show lines 50–80 with line numbers, no other decorations
batty --style=numbers --line-range 50:80 src/lib.rs
# Cursor-centric relative numbering for line 42
batty --line-numbers=relative --highlight-line=42 src/main.rs
# Show only the modified portion of a file with diff markers
batty --diff src/main.rs
# Render plain text from stdin as Python
cat data.py | batty --language python --plain
# Force interactive even though config says interactive = false
batty -i README.md
# One-off non-interactive run despite `interactive = true` in config
batty --no-interactive README.md
batty --paging=never README.md # equivalent
# Skip the user's config for one run
BATTY_CONFIG_PATH=/dev/null batty README.md
# List bundled themes
batty --list-themesThe full list lives in OUT-OF-SCOPE.md. Highlights:
--diff-contextdoesn't restrict output to changed regions; it only sets the diff window passed to git2.--wrapbreaks at column boundaries, not word boundaries — fine for source code, less ideal for prose. Forced off in interactive mode (cursor / viewport / status-bar all assume 1 source line = 1 visual row).- No Windows support (POSIX pager invocation, etc.).
- Interactive mode is keyboard-only: no search, no mouse, no persistent cursor across runs.
- Follow mode polls every 200 ms; a real
inotify/kqueuewatcher would be lower-latency but adds platform code. - Follow mode rebuilds the syntax highlighter on every poll, so multi-line constructs (block comments, multi-line strings) that span a poll boundary may briefly miscolor.
Release history lives in CHANGELOG.md, formatted per
Keep a Changelog.
MIT.