# How bat Manages Output to the Terminal and Pagers: A Deep Dive into the Rust Source Code

> Discover how bat manages terminal and pager output. Explore the Rust source code to understand its three-layer output abstraction and efficient formatting logic.

- Repository: [David Peter/bat](https://github.com/sharkdp/bat)
- Tags: deep-dive
- Published: 2026-03-06

---

**bat writes formatted output either directly to stdout or through a paging program by using a three-layer abstraction involving paging mode configuration, pager discovery, and an output handle system that hides the destination from the formatting logic.**

The `bat` command-line tool by sharkdp/bat is a syntax-highlighting cat clone written in Rust that must gracefully handle output to both terminals and external pagers. Understanding how bat manages output to the terminal and pagers reveals a sophisticated architecture that separates output destination logic from syntax highlighting and formatting concerns.

## The Three Core Concepts Behind bat's Output Management

bat's output system rests on three distinct concepts defined across specific source files: how it decides *whether* to page, *which* pager to use, and *how* to abstract the writing destination.

### Paging Mode Configuration (src/paging.rs)

The `PagingMode` enum in [`src/paging.rs`](https://github.com/sharkdp/bat/blob/main/src/paging.rs) determines under what conditions bat invokes a pager. The three variants are:

- **Always** – Always pipe output through the selected pager
- **QuitIfOneScreen** – Only use a pager if the output exceeds one terminal screen
- **Never** – Write directly to stdout, bypassing any pager logic

This mode is parsed from the `--paging` command-line flag or configuration files and passed downstream to the output construction logic.

### Pager Discovery and Selection (src/pager.rs)

When paging is required, [`src/pager.rs`](https://github.com/sharkdp/bat/blob/main/src/pager.rs) handles the complex task of discovering which pager binary to execute. The selection follows a strict precedence order:

1. Command-line `--pager` flag
2. `BAT_PAGER` environment variable
3. `PAGER` environment variable
4. Fallback to `"less"`

The `PagerKind::from_bin()` function classifies the discovered binary into categories: `Less`, `More`, `Builtin`, `Bat` (self-reference), or `Unknown`. This classification triggers pager-specific argument rewriting. For example, when the pager is `less`, bat automatically injects `-R` (to preserve color codes), `-F` (to quit if one screen), and `-S` (to chop long lines), while also adding `--no-init` for older less versions to prevent screen clearing issues.

### Output Abstraction Layer (src/output.rs)

The `OutputType` enum in [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs) provides the critical abstraction that allows the rest of bat to remain agnostic about where output is going. It defines three variants:

- **Stdout** – Direct terminal output
- **Pager(Child)** – External pager process with piped stdin
- **BuiltinPager** – The integrated `minus` pager implementation

The `handle()` method returns an `OutputHandle` that implements the writer interface. Whether writing to a pipe connected to `less` or directly to stdout, the syntax highlighting logic calls the same `write!` macro, completely hiding the destination complexity.

## Step-by-Step: How bat Decides Where Your Output Goes

When you execute bat, the output path is determined through an eight-step pipeline:

1. **Parse the paging mode** – The CLI option `--paging` (or config equivalent) is converted into a `PagingMode` value (`Always`, `QuitIfOneScreen`, or `Never`).

2. **Create the output object** – `OutputType::from_mode(paging_mode, wrapping_mode, pager_from_config)` is invoked in [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs). If the mode is `Never`, it immediately returns `stdout()`.

3. **Select a pager** – For non-never modes, `try_pager()` calls `pager::get_pager(pager_from_config)` from [`src/pager.rs`](https://github.com/sharkdp/bat/blob/main/src/pager.rs), following the precedence: `--pager` flag → `BAT_PAGER` → `PAGER` → `"less"`.

4. **Classify the pager** – `PagerKind::from_bin()` inspects the binary name to determine if it is `Less`, `More`, `Builtin`, `Bat`, or `Unknown`.

5. **Handle special cases** – If the pager resolves to `bat` itself, an `InvalidPagerValueBat` error prevents infinite recursion. If it is the built-in `minus` pager, a `BuiltinPager` struct is constructed.

6. **Configure external pagers** – For `less`, the code injects `-R` (raw control chars), `-F` (quit-if-one-screen), `-S` (line chopping), and `-K` (quit on interrupt), plus `--no-init` for older versions. Other pagers receive arguments exactly as supplied.

7. **Spawn the pager** – A `std::process::Command` pipes stdin and spawns the child process, storing it in `OutputType::Pager`. If spawning fails, it gracefully falls back to `Stdout`.

8. **Write and cleanup** – The rest of bat obtains a writer via `output.handle()?` and writes syntax-highlighted content. When `OutputType` is dropped, the child pager is waited on and the built-in pager thread is joined, ensuring proper terminal restoration.

## Terminal Handling and ANSI Escape Sequences

Before output reaches the writer handle, syntax highlighting is converted to terminal-ready format in [`src/terminal.rs`](https://github.com/sharkdp/bat/blob/main/src/terminal.rs). The `as_terminal_escaped` function receives a syntect `Style` and produces ANSI-escaped strings, supporting true-color (24-bit) or fallback 256-color palettes depending on terminal capabilities.

This escaped text is then fed into the `OutputHandle`, which may be piping to `less -R` (which understands ANSI codes) or printing directly to a terminal emulator. This separation of concerns allows bat to support complex color schemes regardless of whether the final destination is a pager or the console.

## Code Examples: Working with bat's Output System

The following examples demonstrate the same API that bat uses internally to abstract output destinations.

### Manually Building an OutputType with User Configuration

```rust
// Example: manually building an OutputType respecting a user‑provided pager
let paging_mode = PagingMode::QuitIfOneScreen;
let wrapping_mode = WrappingMode::Normal;        // from src/wrapping.rs
let config_pager = Some("--pager=less -F");    // from bat config file

let mut out = OutputType::from_mode(paging_mode, wrapping_mode, config_pager)
    .expect("Failed to initialise output");

// Get a writer that works for either a pager or stdout
let mut handle = out.handle().expect("Unable to acquire output handle");

// Write coloured text (the same API used throughout bat)
write!(handle, "{}", as_terminal_escaped(style, "Hello, world!", true, true, false, None))
    .expect("Write failed");

// When `out` goes out of scope the pager (if any) is automatically waited on.

```

### Using the Built-in Pager Directly

```rust
use bat::output::{OutputType, OutputHandle};

let mut out = OutputType::BuiltinPager(bat::output::BuiltinPager::new());
let mut handle = out.handle().unwrap();

handle.write_fmt(format_args!("{} lines of output\n", 42)).unwrap();

```

These snippets illustrate how bat's `OutputType` enum abstracts over stdout, external pager processes, and the built-in `minus` pager, allowing the syntax highlighting engine to write without knowing the final destination.

## Summary

- **PagingMode** in [`src/paging.rs`](https://github.com/sharkdp/bat/blob/main/src/paging.rs) determines whether bat uses a pager always, only when output exceeds one screen, or never.
- **Pager selection** in [`src/pager.rs`](https://github.com/sharkdp/bat/blob/main/src/pager.rs) follows strict precedence (CLI flag → `BAT_PAGER` → `PAGER` → `less`) and automatically configures `less` with flags like `-R` and `-F`.
- **OutputType** in [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs) provides a unified writer interface that hides whether output goes to stdout, an external pager process, or the built-in `minus` pager.
- **Terminal handling** in [`src/terminal.rs`](https://github.com/sharkdp/bat/blob/main/src/terminal.rs) generates ANSI escape sequences independently of the output destination, ensuring colors work in both pagers and direct terminal output.
- The system gracefully falls back to stdout if pager spawning fails and prevents infinite recursion when the pager is set to bat itself.

## Frequently Asked Questions

### How does bat decide whether to use a pager or print directly to the terminal?

bat checks the `PagingMode` configuration, which can be set via the `--paging` command-line flag or the `BAT_PAGING` environment variable. If set to `Never`, it writes directly to stdout. If set to `Always` or `QuitIfOneScreen`, it attempts to spawn a pager, falling back to stdout if no pager is available or if spawning fails.

### What pager does bat use if I don't specify one?

If no pager is specified via `--pager`, `BAT_PAGER`, or the standard `PAGER` environment variable, bat defaults to `less`. When using `less`, bat automatically injects the `-R` flag to preserve color codes and `-F` to quit if the content fits on a single screen, ensuring syntax highlighting works correctly in the pager.

### Can bat use its own built-in pager instead of less?

Yes, bat includes an integrated pager based on the `minus` crate. When the pager selection logic in [`src/pager.rs`](https://github.com/sharkdp/bat/blob/main/src/pager.rs) detects a request for the built-in pager, bat constructs a `BuiltinPager` variant of the `OutputType` enum. This runs within the same process rather than spawning an external command, providing seamless scrolling without external dependencies.

### How does bat prevent infinite recursion when the pager is set to bat itself?

The `PagerKind::from_bin()` function in [`src/pager.rs`](https://github.com/sharkdp/bat/blob/main/src/pager.rs) specifically checks if the resolved pager binary points to `bat` itself. If detected, bat returns an `InvalidPagerValueBat` error and aborts before spawning the process, preventing the recursive loop that would otherwise occur if bat tried to page its own output through itself.