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
syntectsyntax 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 inBUILTIN_MAPPINGS. - Custom mappings added via
--map-syntaxtake precedence over built-ins and are inserted viaSyntaxMapping::insert. - Resolution order processes custom mappings first, then built-ins, using
GlobMatcherto test paths and filenames. - Mapping targets determine behavior:
MapToforces a syntax, whileMapToUnknownandMapExtensionToUnknownenable fallback to first-line detection handled byHighlightingAssets::get_syntax_for_pathinsrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →