How bat Detects File Language for Syntax Highlighting: The 5-Stage Algorithm Explained
bat determines file language through a deterministic five-stage pipeline that prioritizes explicit user mappings, file names, extensions, and first-line shebangs before falling back to plain text.
The bat command-line tool by sharkdp/bat delivers syntax highlighting for hundreds of programming languages through an intelligent, multi-layered detection system. Understanding how bat detects the language of a file for syntax highlighting requires examining the Rust source code in src/assets.rs and src/syntax_mapping.rs, which implement a cascading priority algorithm designed for both speed and accuracy.
The Five-Stage Language Detection Pipeline
The detection process is orchestrated by HighlightingAssets::get_syntax in src/assets.rs (lines 210-242). When processing input, bat evaluates these stages in strict sequential order until a match is found.
Stage 1: Explicit CLI Language Override
If the user specifies --language <name> or -l <name>, bat immediately uses the corresponding syntax definition without executing any file analysis. This bypasses all automatic detection heuristics.
Stage 2: Built-in and Runtime Syntax Mappings
Bat first consults SyntaxMapping::get_syntax_for in src/syntax_mapping.rs (lines 148-168). This function matches the full file path against glob patterns from two sources:
- Built-in mappings: Compiled into
BUILTIN_MAPPINGSinsrc/syntax_mapping/builtin.rs(lines 51-84), generated at build time viabuild/syntax_mapping.rs - Runtime mappings: Added via
--map-syntaxor--map-extensionCLI arguments
Mappings can explicitly target a syntax (MapTo("<syntax>")) or defer to later stages (MapToUnknown).
Stage 3: File Name and Extension Analysis
When no mapping matches, bat examines the path components via HighlightingAssets::get_syntax_for_path. This stage executes two specific checks in src/assets.rs:
- File name detection:
get_syntax_for_file_name(lines 172-176) performs exact lookups for names likeDockerfileorMakefileusing the syntect syntax set - Extension detection:
get_syntax_for_file_extension(lines 188-194) extracts the substring after the final.and queriessyntax_set.find_syntax_by_extension
Both functions utilize IgnoredSuffixes to strip backup suffixes like ~ or .bak before matching.
Stage 4: First-Line Heuristic Detection
If path-based detection returns Error::UndetectedSyntax, bat invokes HighlightingAssets::get_first_line_syntax in src/assets.rs (lines 304-316). This reads the file's first line—stripping any UTF-8 BOM—and passes it to syntax_set.find_syntax_by_first_line to detect shebangs (#!/usr/bin/env python), XML declarations (<?xml), or PHP tags (<?php).
Stage 5: Plain Text Fallback
When all detection attempts fail, the UndetectedSyntax error is caught within HighlightingAssets::get_syntax (lines 231-242), triggering a fallback to the plain-text syntax. This guarantees bat always produces readable output even for unknown file types.
Handling Backup Files and Ignored Suffixes
Before executing name or extension comparisons, bat processes filenames through IgnoredSuffixes in src/syntax_mapping/ignored_suffixes.rs. This module strips common backup suffixes (e.g., file.rs~, config.bak) ensuring that temporary and backup files receive the same syntax highlighting as their originals.
Language Detection Code Examples
The detection algorithm exposes a programmatic API that CLI options invoke internally:
// Explicit language via CLI equivalent (e.g., `bat --language Rust file.txt`)
let syntax = assets.get_syntax(Some("Rust"), &mut opened, &mapping)?;
// Standard path-based detection flow
let path = Path::new("src/main.rs");
let syntax_ref = assets.get_syntax_for_path(path, &mapping)?; // Returns "Rust"
// First-line detection for scripts without extensions
let mut input = Input::stdin().with_name(Some("my_script"));
let mut opened = input.open(b"#!/usr/bin/env python\nprint('hi')", None)?;
let syntax = assets.get_syntax(None, &mut opened, &mapping)?; // Returns "Python"
// Adding custom runtime mappings
mapping.insert("*.myext", MappingTarget::MapTo("C")).ok(); // Maps *.myext to C syntax
Summary
- bat implements a deterministic five-stage pipeline for language detection, defined in
src/assets.rsandsrc/syntax_mapping.rs - Syntax mappings (built-in and user-defined) take highest priority after explicit CLI flags, evaluated via
SyntaxMapping::get_syntax_for - File name and extension detection uses exact matching and syntect integration, with
IgnoredSuffixeshandling backup files - First-line heuristics analyze shebangs and markers when path-based methods return
UndetectedSyntax - A plain-text fallback ensures bat never fails to display file contents, even for unrecognized formats
Frequently Asked Questions
How does bat handle files with multiple extensions like .tar.gz?
bat examines the final extension after the last dot via get_syntax_for_file_extension in src/assets.rs (lines 188-194). For .tar.gz, it detects gz. Complex multi-extension logic is typically handled by built-in glob mappings in BUILTIN_MAPPINGS rather than the extension detector itself.
Can I force bat to treat a file as a specific language without changing the file name?
Yes. Use the --language or -l flag to bypass all automatic detection. Alternatively, add a runtime mapping with --map-syntax 'pattern:syntax' which inserts into the SyntaxMapping structure evaluated by get_syntax_for in src/syntax_mapping.rs.
Why does bat correctly highlight my Makefile~ backup but other tools fail?
bat strips ignored suffixes like ~ and .bak via the IgnoredSuffixes processor in src/syntax_mapping/ignored_suffixes.rs before checking the file name against syntax definitions. This ensures backup files inherit the original file's language detection.
What happens if bat cannot detect any language from the file extension or content?
If HighlightingAssets::get_syntax encounters Error::UndetectedSyntax after all detection stages (lines 231-242 in src/assets.rs), it automatically falls back to the plain-text syntax definition. This guarantees output is always readable, even for binary or unrecognized text formats.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →