# Understanding the Controller in bat's Architecture: A Deep Dive into the Core Orchestrator

> Discover the Controller's role in bat's architecture. Learn how this core orchestrator manages configuration, assets, input, and output for efficient code highlighting.

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

---

**The Controller in bat's architecture serves as the central orchestrator that coordinates user configuration, syntax-highlighting assets, input handling, and output rendering through a three-phase initialization, execution, and printing pipeline.**

The `sharkdp/bat` repository implements a sophisticated syntax-highlighting cat clone written in Rust. At the heart of this tool lies the **Controller**, a struct defined in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs) that manages the entire lifecycle of file display operations without unnecessary data cloning.

## What Is the Controller in bat's Architecture?

The Controller acts as the glue layer between bat's configuration system and its output mechanisms. Unlike monolithic designs that scatter logic across modules, bat centralizes coordination in the `Controller` struct, which holds references to the parsed `Config`, `HighlightingAssets`, and an optional `LessOpenPreprocessor`.

By owning only references rather than cloned data, the Controller ensures the rest of the program remains lightweight and testable while handling all side effects—such as I/O operations, paging decisions, and error propagation—in a single, well-encapsulated location.

## The Three Core Phases of the Controller

The Controller's responsibilities divide naturally into three distinct phases, each implemented through specific methods in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs).

### Initialization Phase

During initialization, the `Controller::new` constructor (lines 22-37 in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs)) packages references to existing structures without cloning large assets. This method accepts:

- A reference to the parsed `Config`
- A reference to `HighlightingAssets` (syntax definitions and themes)
- An optional `LessOpenPreprocessor` reference

The constructor returns a `Controller` instance that carries these references through the entire execution lifecycle, ensuring efficient memory usage even when processing multiple large files.

### Execution Phase

The public `run` method delegates to `run_with_error_handler` (lines 39-55), which determines the appropriate output destination—whether stdout, a pager, or a custom `OutputHandle`. This phase handles:

- **Output selection**: Building the appropriate `OutputHandle` based on paging mode
- **Printer construction**: Instantiating either a `SimplePrinter` (for `--paging=never` or `--no-paging`) or an `InteractivePrinter` (which handles paging, line numbers, and syntax highlighting)
- **Input iteration**: Processing each `Input` in the provided vector
- **Error propagation**: Converting internal errors into appropriate exit codes
- **Terminal title updates**: Managing terminal metadata when supported

The Controller abstracts the complexity of deciding whether to page output or stream directly to the terminal, centralizing this logic in one location rather than duplicating it across printer implementations.

### Printing Phase

For each input, the `print_input` method (lines 61-140) orchestrates the actual content processing:

1. **Input opening**: Opens files or streams, optionally applying the `lessopen` preprocessor
2. **Git integration**: Obtains Git diff information when the `git` feature is enabled
3. **Printer selection**: Creates the appropriate printer instance based on configuration
4. **Content streaming**: Delegates to `print_file` or `print_file_ranges` to walk buffered lines, apply line-range filtering, and render headers, footers, and body content

The detailed range loop in `print_file_ranges` (lines 52-89) handles the complex logic of displaying specific line ranges while maintaining proper context and formatting.

## Key Source Files and Implementation Details

Understanding the Controller requires familiarity with several interconnected modules:

| File | Purpose |
|------|---------|
| [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs) | Core `Controller` struct, constructor, `run` pipeline, and printing logic |
| [`src/bin/bat/main.rs`](https://github.com/sharkdp/bat/blob/main/src/bin/bat/main.rs) | Application entry point that constructs the `Controller` and invokes `run` |
| [`src/lib.rs`](https://github.com/sharkdp/bat/blob/main/src/lib.rs) | Re-exports the `controller` module and documents the public API entry point |
| [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs) | Defines `SimplePrinter` and `InteractivePrinter`, the two concrete types the Controller selects between |
| [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs) | Provides `OutputHandle` and `OutputType` for abstracting over stdout, pagers, and custom writers |

The Controller's design demonstrates Rust's ownership model effectively—it holds references to heavy resources (`HighlightingAssets` can be several megabytes of syntax definitions) while coordinating ephemeral operations like opening files and writing output.

## Practical Code Examples

Creating and running a Controller follows the pattern used in bat's binary entry point:

```rust
use bat::config::Config;
use bat::controller::Controller;
use bat::assets::HighlightingAssets;
use bat::input::Input;

// Assume `config` and `assets` have already been built.
let controller = Controller::new(&config, &assets);

// `inputs` can be any combination of files, stdin, or other sources.
let inputs = vec![
    Input::ordinary_file("src/main.rs"),
    Input::stdin(),
];

// Run the controller, letting it decide the appropriate output handle.
// `None` means the controller will create its own OutputHandle (stdout or pager).
match controller.run(inputs, None) {
    Ok(true)  => println!("All files displayed successfully."),
    Ok(false) => eprintln!("Finished with non‑fatal errors."),
    Err(e)    => eprintln!("Fatal error: {}", e),
}

```

The binary entry point in [`src/bin/bat/main.rs`](https://github.com/sharkdp/bat/blob/main/src/bin/bat/main.rs) demonstrates the concise production usage:

```rust
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
let controller = Controller::new(&config, &assets);
controller.run(inputs, None)?;

```

These examples map directly to the implementation in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs), showing how the Controller abstracts the complexity of asset management, printer selection, and output handling behind a simple, reference-based API.

## Summary

- The **Controller** in bat's architecture acts as the central coordinator that binds configuration, assets, and I/O operations without cloning heavy data structures.
- It operates through three distinct phases: **Initialization** (holding references to `Config` and `HighlightingAssets`), **Execution** (determining output type and printer selection), and **Printing** (streaming content through the appropriate printer).
- By centralizing side effects like paging decisions, terminal title updates, and error handling in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs), bat maintains a clean separation between data ownership and operational coordination.
- The design leverages Rust's borrowing system to keep the binary lightweight while supporting complex features like Git integration, line-range filtering, and preprocessor pipelines.

## Frequently Asked Questions

### What makes the Controller different from the Printer in bat's architecture?

The **Controller** is the orchestrator that decides *which* printer to use and *when* to invoke it, while the **Printer** (either `SimplePrinter` or `InteractivePrinter` defined in [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs)) handles the actual formatting and rendering of content. The Controller manages the lifecycle of inputs and outputs, whereas the Printer focuses solely on transforming buffered lines into displayed text with syntax highlighting and decorations.

### How does the Controller handle different output modes like paging versus direct stdout?

The Controller determines the output mode in the `run_with_error_handler` method by constructing an appropriate `OutputHandle` based on the `Config` settings. When paging is enabled, it creates an output handle that spawns a pager process; when disabled, it writes directly to stdout. This abstraction lives in [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs), but the Controller makes the decision and passes the handle to the selected printer implementation.

### Why does the Controller use references instead of owning the Config and HighlightingAssets?

The Controller uses references to avoid cloning large data structures like `HighlightingAssets`, which can contain several megabytes of syntax definitions and themes. By holding `&Config` and `&HighlightingAssets` rather than owned values, the Controller remains lightweight and can be instantiated multiple times without duplicating asset data in memory. This design follows Rust's ownership model to maximize performance while maintaining a clean API surface.

### Where does the Controller fit in bat's module hierarchy?

The Controller is defined in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs) and re-exported at the crate root in [`src/lib.rs`](https://github.com/sharkdp/bat/blob/main/src/lib.rs), making it the primary public API entry point for programmatic usage of bat. The binary entry point in [`src/bin/bat/main.rs`](https://github.com/sharkdp/bat/blob/main/src/bin/bat/main.rs) constructs the Controller and invokes its `run` method, while supporting modules like [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs) and [`src/output.rs`](https://github.com/sharkdp/bat/blob/main/src/output.rs) provide the implementations that the Controller coordinates.