How bat Maps File Extensions or Names to Syntaxes: Complete Technical Guide

bat determines syntax highlighting by matching file paths against a precedence-ordered table of glob patterns, combining compile-time TOML definitions with runtime --map-syntax overrides to resolve either a specific syntax or a fallback to first-line detection.

The bat command-line tool by sharkdp/bat provides syntax highlighting for files by intelligently mapping file extensions and names to specific language syntaxes. Understanding how bat maps file extensions or names to syntaxes reveals a hybrid architecture that blends built-in definitions with user-customizable rules. This mapping system relies on the SyntaxMapping struct in src/syntax_mapping.rs to orchestrate the resolution pipeline.

The Syntax Mapping Architecture

At the core of bat's file-type detection is a syntax-mapping table that associates glob patterns with specific targets. Each mapping entry consists of a pattern (e.g., *.c, Dockerfile) and a MappingTarget that determines how to interpret the match.

Mapping Targets and Glob Patterns

The MappingTarget enum defined in src/syntax_mapping.rs (lines 30-47) supports three distinct resolution strategies:

  • MapTo("syntax name") — Forces bat to use the specified syntax immediately.
  • MapToUnknown — Treats the file name as a hint only, causing bat to fall back to first-line detection (shebang or modeline parsing).
  • MapExtensionToUnknown — Similar to MapToUnknown, but applies exclusively when the pattern matches a file extension (e.g., *.conf), deferring to content-based detection if no syntax matches.

Built-in Mappings from TOML Files

During compilation, a build script scans all *.toml files under src/syntax_mapping/builtins/ and generates a static array named BUILTIN_MAPPINGS in src/syntax_mapping/builtin.rs. Each TOML entry lists a target syntax and a list of glob matchers.

For example, src/syntax_mapping/builtins/common/50-git.toml contains:

[mappings]
"Git Ignore" = ["*.gitignore", "**/.gitignore"]

This generates a MapTo("Git Ignore") entry for both patterns. The build-time code generator in build/syntax_mapping.rs converts these declarative tables into Rust code using MappingDefModel, Matcher, and MappingTarget structures.

Runtime Resolution and Precedence

When bat initializes, it constructs a SyntaxMapping object that merges user-defined rules with the compiled-in defaults.

Adding Custom Mappings via CLI

The --map-syntax flag allows users to inject custom mappings at runtime. Internally, this invokes SyntaxMapping::insert (defined in src/syntax_mapping.rs lines 99-103), which adds a (GlobMatcher, MappingTarget) pair to the custom_mappings vector. For example:


# Treat any *.myext files as C source

bat --map-syntax="*.myext=C" myfile.myext

This command parses the input string and calls SyntaxMapping::insert("*.myext", MappingTarget::MapTo("C")), as implemented in src/bin/bat/app.rs around line 349.

Lookup Order in get_syntax_for

The SyntaxMapping::get_syntax_for method (lines 148-166 in src/syntax_mapping.rs) iterates over all mappings in strict precedence: custom mappings first, then built-in mappings. It uses the globset crate's GlobMatcher to test both the full candidate path and the isolated filename:

pub fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> {
    // try custom + builtin mappings
    for (glob, syntax) in self.all_mappings() {
        if glob.is_match_candidate(&candidate) ||
           candidate_filename.as_ref().map_or(false, |f| glob.is_match_candidate(f)) {
            return Some(*syntax);
        }
    }
    // fallback to ignored-suffix stripping ...
}

If no mapping matches, bat strips ignored suffixes (like .bak or .orig) and repeats the lookup.

Applying Mappings to Syntax Selection

Once get_syntax_for returns a MappingTarget, HighlightingAssets::get_syntax_for_path (in src/assets.rs, lines 58-84) applies the mapping to the actual syntax set:

  • MapTo(syntax_name) — Looks up the syntax name in the bundled syntect syntax set and applies it immediately.
  • MapToUnknown or MapExtensionToUnknown — Returns Error::UndetectedSyntax, triggering the caller to perform first-line detection (shebang parsing).

This separation between mapping resolution and syntax application allows bat to distinguish between "force this syntax" and "use this as a hint only" scenarios.

Practical Code Examples

Override Built-in Rules with Custom Mappings

Force bat to treat a specific filename as Docker syntax, regardless of extension:

bat --map-syntax="Dockerfile=Dockerfile" ./my-docker-file

The glob pattern "Dockerfile" matches the filename portion before any extension handling occurs.

Enable First-Line Detection for Extensions

Map an extension to unknown, allowing bat to examine the shebang line:


# *.conf files should first be examined for a shebang / modeline

bat --map-syntax="*.conf=MapExtensionToUnknown" config.conf

Using MapExtensionToUnknown tells bat to attempt extension-based mapping, but defer to first-line detection if the syntax cannot be determined (see src/assets.rs lines 78-84).

Programmatically Inspect Mappings

To list all active mappings using bat's internal API:

use bat::syntax_mapping::SyntaxMapping;

fn main() {
    let mapping = SyntaxMapping::new(); // loads built-ins only
    for (matcher, target) in mapping.all_mappings() {
        println!("{} → {:?}", matcher.glob(), target);
    }
}

Summary

  • bat maps file extensions or names to syntaxes using a hybrid table of glob patterns and mapping targets defined in src/syntax_mapping.rs.
  • Built-in mappings are generated at compile time from TOML files in src/syntax_mapping/builtins/ and stored in BUILTIN_MAPPINGS.
  • Custom mappings added via --map-syntax take precedence over built-ins and are inserted via SyntaxMapping::insert.
  • Resolution order processes custom mappings first, then built-ins, using GlobMatcher to test paths and filenames.
  • Mapping targets determine behavior: MapTo forces a syntax, while MapToUnknown and MapExtensionToUnknown enable fallback to first-line detection handled by HighlightingAssets::get_syntax_for_path in src/assets.rs.

Frequently Asked Questions

How do I override a built-in syntax mapping in bat?

Use the --map-syntax CLI flag to inject a custom mapping with higher precedence. For example, bat --map-syntax="*.txt=Markdown" file.txt forces bat to treat all .txt files as Markdown, overriding the default Plain Text mapping. These custom rules are stored in the custom_mappings vector and checked before built-in rules during SyntaxMapping::get_syntax_for.

What happens if bat cannot determine a syntax from the file extension?

If no mapping matches or if the mapping target is MapToUnknown or MapExtensionToUnknown, bat returns Error::UndetectedSyntax from HighlightingAssets::get_syntax_for_path. This triggers a fallback mechanism that examines the file's first line for shebangs (#!) or modelines (e.g., vim: set ft=python:) to determine the appropriate syntax.

Where are the built-in syntax mappings defined in the bat source code?

Built-in mappings are defined in TOML files located under src/syntax_mapping/builtins/, organized by category (e.g., common/, git/). During the build process, build/syntax_mapping.rs parses these files and generates the BUILTIN_MAPPINGS static array in src/syntax_mapping/builtin.rs, which is then compiled into the binary.

How does bat handle file names without extensions, like Dockerfile?

bat uses glob patterns that match the full filename, not just extensions. In src/syntax_mapping/builtins/, entries like "Dockerfile" = ["Dockerfile", "**/Dockerfile"] create MapTo targets that match the literal filename. During lookup in SyntaxMapping::get_syntax_for, the matcher tests both the full path and the isolated filename against these patterns, enabling detection of extension-less files like Makefile or Dockerfile.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →