batty-cat 0.9.1

A cat clone with syntax highlighting, git integration, and Rhai support
batty-cat-0.9.1 is not a library.

batty

A from-scratch Rust clone of batcat with syntax highlighting, git diff markers, and a pager. Plus:

  • Bundled Rhai grammar.rhai scripts 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 like glow, with a m toggle in interactive mode.
  • Tail / follow mode (-f) — tail -f semantics with syntax highlighting.
  • TOML config~/.config/batty/config.toml.
  • Small release binary — ~2.8 MB with full grammar/theme bundles.

Targets macOS and Linux.


Installation

Homebrew (macOS / Linux)

brew tap codedeviate/cli
brew install batty

The formula builds from source via cargo, so a Rust toolchain is pulled in as a build-time dependency and removed afterwards.

Cargo (crates.io)

cargo install batty-cat

The 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.

Build from source

git clone https://github.com/codedeviate/batty.git && cd batty
cargo build --release
# Binary lands at target/release/batty

Move it onto your $PATH however you like (e.g. cp target/release/batty ~/.local/bin/).

Requires Rust 1.86 or newer.


Quick start

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 themes

Flag reference

batty accepts the flags below. Many can also be set in the config file.

Input

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.

Display

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 / auto. Default: auto. character and auto break 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. --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.

Theme & color

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.

Markdown rendering

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).

Interactive mode

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.

Tail / follow

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.

Pager

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.

Discovery

Flag Description
-L, --list-languages Print every supported language (one per line) and exit.
--list-themes Print every bundled theme and exit.
-h, --help Short help.
--help Long help with full descriptions.
-V, --version Print version.

Flag-conflict semantics

  • All boolean flags use overrides_with so config + CLI can both set a flag without cannot be used multiple times errors.
  • --interactive and --no-interactive are mutual overrides — last occurrence wins.
  • --paging=never implies --no-interactive.

Config file

~/.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
tail-lines     = 10
highlight-line = [10, 20]
encoding       = "auto"      # "utf-8" | "iso-8859-1" | "auto" (default)

Mapping rules

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.

BATTY_CONFIG_PATH

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 config

Interactive mode

batty -i src/main.rs

Enters 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.


Environment variables

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.

Examples

# 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-themes

Limitations

The full list lives in OUT-OF-SCOPE.md. Highlights:

  • --diff-context doesn't restrict output to changed regions; it only sets the diff window passed to git2.
  • --wrap breaks 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/kqueue watcher 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.

License

MIT.