1
0
mirror of https://github.com/sharkdp/bat synced 2026-08-01 18:31:46 +00:00

Add --fallback-syntax for undetected files (#3617)

* feat(cli): add fallback syntax option

Expose a new fallback syntax CLI option so users can opt into syntax highlighting only when auto-detection fails.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* feat(syntax): apply fallback only after detection fails

Use the fallback syntax only when path and first-line detection fail, preserving existing behavior for detected files and explicit language selection.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(cli): cover fallback syntax behavior

Add integration coverage for fallback syntax usage, precedence with --language, and no-op behavior when syntax is already detected; update help snapshots for the new option.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* docs(changelog): document fallback syntax option

Record the new fallback syntax feature in the unreleased changelog section.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Rizky Mirzaviandy Priambodo
2026-03-08 10:18:29 +07:00
committed by GitHub
parent ab80bd9717
commit 844bfded50
9 changed files with 166 additions and 9 deletions
+17 -8
View File
@@ -210,6 +210,7 @@ impl HighlightingAssets {
pub(crate) fn get_syntax(
&self,
language: Option<&str>,
fallback_syntax: Option<&str>,
input: &mut OpenedInput,
mapping: &SyntaxMapping,
) -> Result<SyntaxReferenceInSet<'_>> {
@@ -234,9 +235,16 @@ impl HighlightingAssets {
match path_syntax {
// If a path wasn't provided, or if path based syntax detection
// above failed, we fall back to first-line syntax detection.
Err(Error::UndetectedSyntax(path)) => self
.get_first_line_syntax(&mut input.reader)?
.ok_or(Error::UndetectedSyntax(path)),
Err(Error::UndetectedSyntax(path)) => {
if let Some(syntax_in_set) = self.get_first_line_syntax(&mut input.reader)? {
Ok(syntax_in_set)
} else if let Some(language) = fallback_syntax {
self.find_syntax_by_token(language)?
.ok_or_else(|| Error::UnknownSyntax(language.to_owned()))
} else {
Err(Error::UndetectedSyntax(path))
}
}
_ => path_syntax,
}
}
@@ -416,11 +424,12 @@ mod tests {
fn get_syntax_name(
&self,
language: Option<&str>,
fallback_syntax: Option<&str>,
input: &mut OpenedInput,
mapping: &SyntaxMapping,
) -> String {
self.assets
.get_syntax(language, input, mapping)
.get_syntax(language, fallback_syntax, input, mapping)
.map(|syntax_in_set| syntax_in_set.syntax.name.clone())
.unwrap_or_else(|_| "!no syntax!".to_owned())
}
@@ -440,7 +449,7 @@ mod tests {
let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
}
fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
@@ -450,7 +459,7 @@ mod tests {
let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
}
#[cfg(unix)]
@@ -470,7 +479,7 @@ mod tests {
let input = Input::stdin().with_name(Some(file_name));
let mut opened_input = input.open(content, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
}
fn syntax_is_same_for_inputkinds(&self, file_name: &str, content: &str) -> bool {
@@ -752,7 +761,7 @@ contexts:
let mut opened_input = input.open(dummy_stdin, None).unwrap();
assert_eq!(
test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping),
test.get_syntax_name(None, None, &mut opened_input, &test.syntax_mapping),
"SSH Config"
);
}
+4
View File
@@ -384,6 +384,10 @@ impl App {
None
}
}),
fallback_syntax: self
.matches
.get_one::<String>("fallback-syntax")
.map(|s| s.as_str()),
show_nonprintable: self.matches.get_flag("show-all"),
nonprintable_notation: match self
.matches
+11
View File
@@ -120,6 +120,17 @@ pub fn build_app(interactive_output: bool) -> Command {
language names and file extensions.",
),
)
.arg(
Arg::new("fallback-syntax")
.long("fallback-syntax")
.visible_alias("fallback-language")
.help("Set a fallback language for undetected syntaxes.")
.long_help(
"Set a fallback language for syntax highlighting when auto-detection fails. \
Unlike '--language', this is only used when no syntax could be detected from \
filename, custom syntax mappings, or first-line detection.",
),
)
.arg(
Arg::new("highlight-line")
.long("highlight-line")
+3
View File
@@ -38,6 +38,9 @@ pub struct Config<'a> {
/// The explicitly configured language, if any
pub language: Option<&'a str>,
/// The fallback syntax used when auto-detection fails
pub fallback_syntax: Option<&'a str>,
/// Whether or not to show/replace non-printable characters like space, tab and newline.
pub show_nonprintable: bool,
+6 -1
View File
@@ -268,7 +268,12 @@ impl<'a> InteractivePrinter<'a> {
const PLAIN_TEXT_SYNTAX: &str = "Plain Text";
const MANPAGE_SYNTAX: &str = "Manpage";
const COMMAND_HELP_SYNTAX: &str = "Command Help";
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
match assets.get_syntax(
config.language,
config.fallback_syntax,
input,
&config.syntax_mapping,
) {
Ok(syntax_in_set) => (
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
syntax_in_set.syntax.name == MANPAGE_SYNTAX