From ba97230b9854da12cb6b861d9a7823c667e51d21 Mon Sep 17 00:00:00 2001
From: Michael Vorburger
Date: Sun, 1 Feb 2026 13:34:33 +0100
Subject: [PATCH 001/130] feat: Map BUILD to Python (Starlark) for Bazel (fixes
#3575)
See #3575.
---
src/syntax_mapping/builtins/common/50-bazel.toml | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 src/syntax_mapping/builtins/common/50-bazel.toml
diff --git a/src/syntax_mapping/builtins/common/50-bazel.toml b/src/syntax_mapping/builtins/common/50-bazel.toml
new file mode 100644
index 00000000..5f44ae2d
--- /dev/null
+++ b/src/syntax_mapping/builtins/common/50-bazel.toml
@@ -0,0 +1,2 @@
+[mappings]
+"Python" = ["BUILD"]
From 5a4a7de9338be2b888674259ee29db3ff6b9b6fe Mon Sep 17 00:00:00 2001
From: Michael Vorburger
Date: Sun, 1 Feb 2026 13:40:51 +0100
Subject: [PATCH 002/130] docs: Document mapping BUILD to Python (Starlark) for
Bazel in CHANGELOG.md (see #3576)
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9934329f..bfc516c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,10 @@
- Add `--quiet-empty` (`-E`) flag to suppress output when input is empty. Closes #1936, see #3563 (@NORMAL-EX)
- Improve native man pages and command help syntax highlighting by stripping overstriking, see #3517 (@akirk)
+- Map BUILD to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
+
- `--help` now correctly honors `--pager=builtin`. See #3516 (@keith-hall)
- `--help` now correctly honors custom themes. See #3524 (@keith-hall)
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
From 652d50e92edb239ecb594bf4a3e9d5526983b019 Mon Sep 17 00:00:00 2001
From: zachvalenta
Date: Sun, 8 Mar 2026 16:09:42 -0400
Subject: [PATCH 003/130] Makefile syntax for justfiles
---
CHANGELOG.md | 1 +
src/syntax_mapping/builtins/common/50-just.toml | 2 ++
2 files changed, 3 insertions(+)
create mode 100644 src/syntax_mapping/builtins/common/50-just.toml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d30114d..5d1ee483 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
## Features
+- Map `justfile`, `Justfile`, `.justfile`, and `*.justfile` to Makefile syntax highlighting, see #XXXX (@zachvalenta)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
diff --git a/src/syntax_mapping/builtins/common/50-just.toml b/src/syntax_mapping/builtins/common/50-just.toml
new file mode 100644
index 00000000..5ca19c85
--- /dev/null
+++ b/src/syntax_mapping/builtins/common/50-just.toml
@@ -0,0 +1,2 @@
+[mappings]
+"Makefile" = ["justfile", "Justfile", ".justfile", "*.justfile"]
From 56fe0fa2260675c2603607ac35c4c367e5d8d797 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Sat, 14 Mar 2026 09:18:56 +0200
Subject: [PATCH 004/130] Add case-sensitive glob support to syntax mapping
to allow us to map `BUILD` case sensitively to Python for Skylark
---
build/syntax_mapping.rs | 40 +++++++++++++--
src/syntax_mapping.rs | 50 +++++++++++++++++--
src/syntax_mapping/builtin.rs | 9 ++--
src/syntax_mapping/builtins/README.md | 6 +--
.../builtins/common/50-bazel.toml | 2 +-
5 files changed, 91 insertions(+), 16 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index 64be4bb9..f17b1cf5 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -51,7 +51,13 @@ impl ToTokens for MappingTarget {
/// A single matcher.
///
/// Codegen converts this into a `Lazy>`.
-struct Matcher(Vec);
+struct Matcher {
+ segments: Vec,
+ /// Whether the glob pattern should be matched case-insensitively.
+ ///
+ /// Defaults to `true` (case-insensitive) for backwards compatibility.
+ case_insensitive: bool,
+}
/// Parse a matcher.
///
/// Note that this implementation is rather strict: it will greedily interpret
@@ -116,18 +122,24 @@ impl FromStr for Matcher {
bail!(r#"Invalid matcher: "{s}""#);
}
- Ok(Self(non_empty_segments))
+ Ok(Self {
+ segments: non_empty_segments,
+ case_insensitive: true,
+ })
}
}
impl ToTokens for Matcher {
fn to_tokens(&self, tokens: &mut TokenStream) {
- let t = match self.0.as_slice() {
+ let case_insensitive = self.case_insensitive;
+ let t = match self.segments.as_slice() {
[] => unreachable!("0-length matcher should never be created"),
[MatcherSegment::Text(text)] => {
- quote! { Lazy::new(|| Some(build_matcher_fixed(#text))) }
+ quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case_insensitive))) }
}
// parser logic ensures that this case can only happen when there are dynamic segments
- segs @ [_, ..] => quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ])) },
+ segs @ [_, ..] => {
+ quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case_insensitive)) }
+ }
};
tokens.append_all(t);
}
@@ -175,7 +187,12 @@ impl MatcherSegment {
/// A struct that models a single .toml file in /src/syntax_mapping/builtins/.
#[derive(Clone, Debug, Deserialize)]
struct MappingDefModel {
+ #[serde(default)]
mappings: IndexMap>,
+ /// Case-sensitive mappings. Unlike `mappings`, these glob patterns are
+ /// matched case-sensitively.
+ #[serde(default)]
+ case_sensitive_mappings: IndexMap>,
}
impl MappingDefModel {
fn into_mapping_list(self) -> MappingList {
@@ -188,6 +205,19 @@ impl MappingDefModel {
.map(|matcher| (matcher, target.clone()))
.collect::>()
})
+ .chain(
+ self.case_sensitive_mappings
+ .into_iter()
+ .flat_map(|(target, matchers)| {
+ matchers
+ .into_iter()
+ .map(|mut matcher| {
+ matcher.case_insensitive = false;
+ (matcher, target.clone())
+ })
+ .collect::>()
+ }),
+ )
.collect();
MappingList(list)
}
diff --git a/src/syntax_mapping.rs b/src/syntax_mapping.rs
index 0cd2d655..8fc4008a 100644
--- a/src/syntax_mapping.rs
+++ b/src/syntax_mapping.rs
@@ -17,9 +17,9 @@ use ignored_suffixes::IgnoredSuffixes;
mod builtin;
pub mod ignored_suffixes;
-fn make_glob_matcher(from: &str) -> Result {
+fn make_glob_matcher(from: &str, case_insensitive: bool) -> Result {
let matcher = GlobBuilder::new(from)
- .case_insensitive(true)
+ .case_insensitive(case_insensitive)
.literal_separator(true)
.build()?
.compile_matcher();
@@ -97,7 +97,14 @@ impl<'a> SyntaxMapping<'a> {
}
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
- let matcher = make_glob_matcher(from)?;
+ let matcher = make_glob_matcher(from, true)?;
+ self.custom_mappings.push((matcher, to));
+ Ok(())
+ }
+
+ /// Like [`Self::insert`], but the glob pattern is matched case-sensitively.
+ pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
+ let matcher = make_glob_matcher(from, false)?;
self.custom_mappings.push((matcher, to));
Ok(())
}
@@ -261,4 +268,41 @@ mod tests {
Some(MappingTarget::MapTo("alpha"))
);
}
+
+ #[test]
+ fn case_sensitive_custom_mappings_work() {
+ let mut map = SyntaxMapping::new();
+ map.insert_case_sensitive("MY_SPECIAL_FILE", MappingTarget::MapTo("Python"))
+ .ok();
+
+ // Exact case matches
+ assert_eq!(
+ map.get_syntax_for("/path/to/MY_SPECIAL_FILE"),
+ Some(MappingTarget::MapTo("Python"))
+ );
+ // Different case should NOT match the case-sensitive rule
+ assert_eq!(map.get_syntax_for("/path/to/my_special_file"), None);
+ assert_eq!(map.get_syntax_for("/path/to/My_Special_File"), None);
+ }
+
+ #[test]
+ fn builtin_mappings_build_is_case_sensitive() {
+ let map = SyntaxMapping::new();
+
+ // "BUILD" (uppercase) should map to Python via case-sensitive builtin
+ assert_eq!(
+ map.get_syntax_for("/path/to/BUILD"),
+ Some(MappingTarget::MapTo("Python"))
+ );
+ // "build" (lowercase) should still map to MapToUnknown
+ assert_eq!(
+ map.get_syntax_for("/path/to/build"),
+ Some(MappingTarget::MapToUnknown)
+ );
+ // Mixed case should NOT match the Python rule
+ assert_eq!(
+ map.get_syntax_for("/path/to/Build"),
+ Some(MappingTarget::MapToUnknown)
+ );
+ }
}
diff --git a/src/syntax_mapping/builtin.rs b/src/syntax_mapping/builtin.rs
index 1822be57..52f1d47b 100644
--- a/src/syntax_mapping/builtin.rs
+++ b/src/syntax_mapping/builtin.rs
@@ -53,8 +53,9 @@ include!(concat!(
/// A failure to compile is a fatal error.
///
/// Used internally by `Lazy>`'s lazy evaluation closure.
-fn build_matcher_fixed(from: &str) -> GlobMatcher {
- make_glob_matcher(from).expect("A builtin fixed glob matcher failed to compile")
+fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher {
+ make_glob_matcher(from, case_insensitive)
+ .expect("A builtin fixed glob matcher failed to compile")
}
/// Join a list of matcher segments to create a glob string, replacing all
@@ -64,7 +65,7 @@ fn build_matcher_fixed(from: &str) -> GlobMatcher {
/// to compile.
///
/// Used internally by `Lazy >`'s lazy evaluation closure.
-fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option {
+fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Option {
// join segments
let mut buf = String::new();
for seg in segs {
@@ -77,7 +78,7 @@ fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option {
}
}
// compile glob matcher
- let matcher = make_glob_matcher(&buf).ok()?;
+ let matcher = make_glob_matcher(&buf, case_insensitive).ok()?;
Some(matcher)
}
diff --git a/src/syntax_mapping/builtins/README.md b/src/syntax_mapping/builtins/README.md
index 29cf43ee..7152fd33 100644
--- a/src/syntax_mapping/builtins/README.md
+++ b/src/syntax_mapping/builtins/README.md
@@ -20,9 +20,9 @@ syntax mappings defined by all TOML files, and embed them into the binary.
## File syntax
-Each TOML file should contain a single section named `mappings`, with each of
-its keys being a language identifier (first column of `bat -L`; also referred to
-as "target").
+Each TOML file should contain a single section named `mappings` and/or a single
+section named `case_sensitive_mappings`, with each of its keys being a language
+identifier (first column of `bat -L`; also referred to as "target").
The value of each key should be an array of strings, with each item being a glob
matcher. We will call each of these items a "rule".
diff --git a/src/syntax_mapping/builtins/common/50-bazel.toml b/src/syntax_mapping/builtins/common/50-bazel.toml
index 5f44ae2d..2b0e17e2 100644
--- a/src/syntax_mapping/builtins/common/50-bazel.toml
+++ b/src/syntax_mapping/builtins/common/50-bazel.toml
@@ -1,2 +1,2 @@
-[mappings]
+[case_sensitive_mappings]
"Python" = ["BUILD"]
From 2a29802dd57a202589e555e896432575225fe76b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Mar 2026 03:35:38 +0000
Subject: [PATCH 005/130] Refactor: string-or-struct matcher syntax, Case enum,
remove case_sensitive_mappings table
Co-authored-by: keith-hall <11882719+keith-hall@users.noreply.github.com>
---
build/syntax_mapping.rs | 83 +++++++++++++------
src/syntax_mapping.rs | 15 +++-
src/syntax_mapping/builtin.rs | 10 +--
.../builtins/common/50-bazel.toml | 4 +-
4 files changed, 76 insertions(+), 36 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index f17b1cf5..17722f67 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -47,16 +47,34 @@ impl ToTokens for MappingTarget {
}
}
-#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)]
+/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
+///
+/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+enum Case {
+ Sensitive,
+ Insensitive,
+}
+impl ToTokens for Case {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ let t = match self {
+ Self::Sensitive => quote! { Case::Sensitive },
+ Self::Insensitive => quote! { Case::Insensitive },
+ };
+ tokens.append_all(t);
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
/// A single matcher.
///
/// Codegen converts this into a `Lazy>`.
struct Matcher {
segments: Vec,
- /// Whether the glob pattern should be matched case-insensitively.
+ /// Whether the glob pattern should be matched case-sensitively.
///
- /// Defaults to `true` (case-insensitive) for backwards compatibility.
- case_insensitive: bool,
+ /// Defaults to `Case::Insensitive` for backwards compatibility.
+ case: Case,
}
/// Parse a matcher.
///
@@ -124,21 +142,53 @@ impl FromStr for Matcher {
Ok(Self {
segments: non_empty_segments,
- case_insensitive: true,
+ case: Case::Insensitive,
})
}
}
+
+/// Helper type for deserializing a `Matcher` from either a plain string or a
+/// `{ glob = "...", case_sensitive = true }` struct.
+#[derive(Deserialize)]
+#[serde(untagged)]
+enum RawMatcher {
+ Simple(String),
+ Full {
+ glob: String,
+ #[serde(default)]
+ case_sensitive: bool,
+ },
+}
+
+impl<'de> serde::Deserialize<'de> for Matcher {
+ fn deserialize>(deserializer: D) -> Result {
+ let raw = RawMatcher::deserialize(deserializer)?;
+ match raw {
+ RawMatcher::Simple(s) => Matcher::from_str(&s).map_err(serde::de::Error::custom),
+ RawMatcher::Full { glob, case_sensitive } => {
+ let mut matcher =
+ Matcher::from_str(&glob).map_err(serde::de::Error::custom)?;
+ matcher.case = if case_sensitive {
+ Case::Sensitive
+ } else {
+ Case::Insensitive
+ };
+ Ok(matcher)
+ }
+ }
+ }
+}
impl ToTokens for Matcher {
fn to_tokens(&self, tokens: &mut TokenStream) {
- let case_insensitive = self.case_insensitive;
+ let case = &self.case;
let t = match self.segments.as_slice() {
[] => unreachable!("0-length matcher should never be created"),
[MatcherSegment::Text(text)] => {
- quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case_insensitive))) }
+ quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case))) }
}
// parser logic ensures that this case can only happen when there are dynamic segments
segs @ [_, ..] => {
- quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case_insensitive)) }
+ quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case)) }
}
};
tokens.append_all(t);
@@ -189,10 +239,6 @@ impl MatcherSegment {
struct MappingDefModel {
#[serde(default)]
mappings: IndexMap>,
- /// Case-sensitive mappings. Unlike `mappings`, these glob patterns are
- /// matched case-sensitively.
- #[serde(default)]
- case_sensitive_mappings: IndexMap>,
}
impl MappingDefModel {
fn into_mapping_list(self) -> MappingList {
@@ -205,19 +251,6 @@ impl MappingDefModel {
.map(|matcher| (matcher, target.clone()))
.collect::>()
})
- .chain(
- self.case_sensitive_mappings
- .into_iter()
- .flat_map(|(target, matchers)| {
- matchers
- .into_iter()
- .map(|mut matcher| {
- matcher.case_insensitive = false;
- (matcher, target.clone())
- })
- .collect::>()
- }),
- )
.collect();
MappingList(list)
}
diff --git a/src/syntax_mapping.rs b/src/syntax_mapping.rs
index 8fc4008a..584a5cb6 100644
--- a/src/syntax_mapping.rs
+++ b/src/syntax_mapping.rs
@@ -17,9 +17,16 @@ use ignored_suffixes::IgnoredSuffixes;
mod builtin;
pub mod ignored_suffixes;
-fn make_glob_matcher(from: &str, case_insensitive: bool) -> Result {
+/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum Case {
+ Sensitive,
+ Insensitive,
+}
+
+fn make_glob_matcher(from: &str, case: Case) -> Result {
let matcher = GlobBuilder::new(from)
- .case_insensitive(case_insensitive)
+ .case_insensitive(matches!(case, Case::Insensitive))
.literal_separator(true)
.build()?
.compile_matcher();
@@ -97,14 +104,14 @@ impl<'a> SyntaxMapping<'a> {
}
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
- let matcher = make_glob_matcher(from, true)?;
+ let matcher = make_glob_matcher(from, Case::Insensitive)?;
self.custom_mappings.push((matcher, to));
Ok(())
}
/// Like [`Self::insert`], but the glob pattern is matched case-sensitively.
pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
- let matcher = make_glob_matcher(from, false)?;
+ let matcher = make_glob_matcher(from, Case::Sensitive)?;
self.custom_mappings.push((matcher, to));
Ok(())
}
diff --git a/src/syntax_mapping/builtin.rs b/src/syntax_mapping/builtin.rs
index 52f1d47b..41007be5 100644
--- a/src/syntax_mapping/builtin.rs
+++ b/src/syntax_mapping/builtin.rs
@@ -3,7 +3,7 @@ use std::env;
use globset::GlobMatcher;
use once_cell::sync::Lazy;
-use crate::syntax_mapping::{make_glob_matcher, MappingTarget};
+use crate::syntax_mapping::{make_glob_matcher, Case, MappingTarget};
// Static syntax mappings generated from /src/syntax_mapping/builtins/ by the
// build script (/build/syntax_mapping.rs).
@@ -53,8 +53,8 @@ include!(concat!(
/// A failure to compile is a fatal error.
///
/// Used internally by `Lazy>`'s lazy evaluation closure.
-fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher {
- make_glob_matcher(from, case_insensitive)
+fn build_matcher_fixed(from: &str, case: Case) -> GlobMatcher {
+ make_glob_matcher(from, case)
.expect("A builtin fixed glob matcher failed to compile")
}
@@ -65,7 +65,7 @@ fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher {
/// to compile.
///
/// Used internally by `Lazy >`'s lazy evaluation closure.
-fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Option {
+fn build_matcher_dynamic(segs: &[MatcherSegment], case: Case) -> Option {
// join segments
let mut buf = String::new();
for seg in segs {
@@ -78,7 +78,7 @@ fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Opt
}
}
// compile glob matcher
- let matcher = make_glob_matcher(&buf, case_insensitive).ok()?;
+ let matcher = make_glob_matcher(&buf, case).ok()?;
Some(matcher)
}
diff --git a/src/syntax_mapping/builtins/common/50-bazel.toml b/src/syntax_mapping/builtins/common/50-bazel.toml
index 2b0e17e2..2ced1399 100644
--- a/src/syntax_mapping/builtins/common/50-bazel.toml
+++ b/src/syntax_mapping/builtins/common/50-bazel.toml
@@ -1,2 +1,2 @@
-[case_sensitive_mappings]
-"Python" = ["BUILD"]
+[mappings]
+"Python" = [{ glob = "BUILD", case_sensitive = true }]
From c6e661d80bc9654583771f2ccd00aa1bf4a3e28a Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Wed, 18 Mar 2026 22:25:07 +0200
Subject: [PATCH 006/130] cargo fmt
---
build/syntax_mapping.rs | 8 +++++---
src/syntax_mapping/builtin.rs | 3 +--
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index 17722f67..11d778e3 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -165,9 +165,11 @@ impl<'de> serde::Deserialize<'de> for Matcher {
let raw = RawMatcher::deserialize(deserializer)?;
match raw {
RawMatcher::Simple(s) => Matcher::from_str(&s).map_err(serde::de::Error::custom),
- RawMatcher::Full { glob, case_sensitive } => {
- let mut matcher =
- Matcher::from_str(&glob).map_err(serde::de::Error::custom)?;
+ RawMatcher::Full {
+ glob,
+ case_sensitive,
+ } => {
+ let mut matcher = Matcher::from_str(&glob).map_err(serde::de::Error::custom)?;
matcher.case = if case_sensitive {
Case::Sensitive
} else {
diff --git a/src/syntax_mapping/builtin.rs b/src/syntax_mapping/builtin.rs
index 41007be5..79d298c3 100644
--- a/src/syntax_mapping/builtin.rs
+++ b/src/syntax_mapping/builtin.rs
@@ -54,8 +54,7 @@ include!(concat!(
///
/// Used internally by `Lazy>`'s lazy evaluation closure.
fn build_matcher_fixed(from: &str, case: Case) -> GlobMatcher {
- make_glob_matcher(from, case)
- .expect("A builtin fixed glob matcher failed to compile")
+ make_glob_matcher(from, case).expect("A builtin fixed glob matcher failed to compile")
}
/// Join a list of matcher segments to create a glob string, replacing all
From 618d7340bb63fa6168615585075c6debb7240a82 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:34:12 -0700
Subject: [PATCH 007/130] fix: consistent .deb MUSL package names
Fix two inconsistencies in MUSL .deb package naming:
1. Change dpkg_arch for aarch64-unknown-linux-musl from `arm64` to
`musl-linux-arm64` to match the convention used by other MUSL targets.
2. Change the DPKG_BASENAME case pattern from `*-musl)` to `*-musl*)`
so it also matches `musleabihf`, giving the arm target the `bat-musl`
prefix like all other MUSL packages.
After this fix, all MUSL .deb packages follow the consistent pattern:
`bat-musl_VERSION_musl-linux-ARCH.deb`
Closes #3482
---
.github/workflows/CICD.yml | 4 ++--
CHANGELOG.md | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml
index f1bc3887..e1b82424 100644
--- a/.github/workflows/CICD.yml
+++ b/.github/workflows/CICD.yml
@@ -161,7 +161,7 @@ jobs:
fail-fast: false
matrix:
job:
- - { target: aarch64-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: arm64, use-cross: true }
+ - { target: aarch64-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: musl-linux-arm64, use-cross: true }
- { target: aarch64-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: arm64, use-cross: true }
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest , dpkg_arch: armhf, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-latest , dpkg_arch: musl-linux-armhf, use-cross: true }
@@ -335,7 +335,7 @@ jobs:
DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}
DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl
- case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
+ case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}
DPKG_ARCH="${{ matrix.job.dpkg_arch }}"
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7de41716..ea1475bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
## Bugfixes
+- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix), see #3482 (@mvanhorn)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
From 2a3ed948ecc1f98dc7347ddf170ffcd384139ca1 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:35:48 -0700
Subject: [PATCH 008/130] feat: preserve change markers when combining --diff
with --plain
When --plain is set (via CLI or config file), --diff output loses all
visual markers, making it indistinguishable from plain text. The diff
line filtering still works, but without Changes markers or Snip
separators the output is not useful.
Automatically include Changes and Snip style components when --diff is
active alongside --plain. This preserves the --plain intent (no grid,
no header, no line numbers) while keeping diff output readable.
Closes #3630
---
CHANGELOG.md | 1 +
src/bin/bat/app.rs | 11 ++++++++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7de41716..a9508f39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
## Features
+- Preserve `--diff` change markers and snip separators when `--plain` is set, see #3630 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs
index dddb5559..a580ff02 100644
--- a/src/bin/bat/app.rs
+++ b/src/bin/bat/app.rs
@@ -590,7 +590,16 @@ impl App {
// Plain if `--plain` is specified at least once.
if self.matches.get_count("plain") > 0 {
- return Some(StyleComponents(HashSet::from([StyleComponent::Plain])));
+ let mut components = HashSet::from([StyleComponent::Plain]);
+ // When --diff is active, preserve change markers and snip separators
+ // so that diff output remains visually useful.
+ if self.matches.try_contains_id("diff").unwrap_or_default()
+ && self.matches.get_flag("diff")
+ {
+ components.insert(StyleComponent::Changes);
+ components.insert(StyleComponent::Snip);
+ }
+ return Some(StyleComponents(components));
}
// Default behavior.
From 99c8e15c27c7fc03c9b50aa494f7d0db14885040 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:37:45 -0700
Subject: [PATCH 009/130] fix: update changelog entry with PR number for CI
check
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea1475bb..c3c893b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
## Bugfixes
-- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix), see #3482 (@mvanhorn)
+- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
From 5e140558b13f6b87e1dc74ac67cabeb48fc773e4 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:38:03 -0700
Subject: [PATCH 010/130] fix: update changelog entry with PR number for CI
check
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9508f39..457b5cf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@
## Features
-- Preserve `--diff` change markers and snip separators when `--plain` is set, see #3630 (@mvanhorn)
+- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
From 169dc7c45b0c5aacd386c46eb40c9e8abf6cad8b Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 20 Mar 2026 10:33:33 -0700
Subject: [PATCH 011/130] test: add integration tests for --diff combined with
--plain
---
tests/integration_tests.rs | 146 +++++++++++++++++++++++++++++++++++++
1 file changed, 146 insertions(+)
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index cfbad253..9a8a7f41 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -3965,3 +3965,149 @@ fn word_wrap_short_line_no_wrap() {
.success()
.stdout("Single Line\n");
}
+
+#[cfg(unix)]
+#[cfg(feature = "git")]
+fn setup_diff_test_repo() -> tempfile::TempDir {
+ use std::process::Command;
+
+ let dir = tempfile::tempdir().expect("can create temporary directory");
+ let repo = dir.path();
+
+ // Initialize a git repo and commit a file
+ Command::new("git")
+ .args(["init"])
+ .current_dir(repo)
+ .output()
+ .expect("git init");
+
+ Command::new("git")
+ .args(["config", "user.email", "test@test.com"])
+ .current_dir(repo)
+ .output()
+ .expect("git config email");
+
+ Command::new("git")
+ .args(["config", "user.name", "Test"])
+ .current_dir(repo)
+ .output()
+ .expect("git config name");
+
+ std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n")
+ .expect("can write test file");
+
+ Command::new("git")
+ .args(["add", "test.txt"])
+ .current_dir(repo)
+ .output()
+ .expect("git add");
+
+ Command::new("git")
+ .args(["commit", "-m", "initial"])
+ .current_dir(repo)
+ .output()
+ .expect("git commit");
+
+ // Modify the file so --diff has something to show
+ std::fs::write(repo.join("test.txt"), "line 1\nline 2 modified\nline 3\nline 4 added\n")
+ .expect("can write modified test file");
+
+ dir
+}
+
+#[cfg(unix)]
+#[cfg(feature = "git")]
+#[test]
+fn diff_plain_preserves_change_markers() {
+ let repo = setup_diff_test_repo();
+
+ // With --diff --plain, output should contain the change marker column
+ // but not other decorations like line numbers or grid
+ let output = bat()
+ .current_dir(repo.path())
+ .arg("--diff")
+ .arg("--plain")
+ .arg("--color=never")
+ .arg("--decorations=always")
+ .arg("test.txt")
+ .assert()
+ .success()
+ .get_output()
+ .stdout
+ .clone();
+
+ let stdout = std::str::from_utf8(&output).expect("valid utf-8");
+
+ // The output should contain the modified and added lines
+ assert!(
+ stdout.contains("line 2 modified"),
+ "diff plain output should contain modified line, got: {stdout}"
+ );
+ assert!(
+ stdout.contains("line 4 added"),
+ "diff plain output should contain added line, got: {stdout}"
+ );
+
+ // Should NOT contain line numbers (a decoration that --plain disables)
+ assert!(
+ !stdout.contains(" 1"),
+ "diff plain output should not contain line numbers, got: {stdout}"
+ );
+}
+
+#[cfg(unix)]
+#[cfg(feature = "git")]
+#[test]
+fn diff_plain_does_not_show_grid_or_header() {
+ let repo = setup_diff_test_repo();
+
+ let output = bat()
+ .current_dir(repo.path())
+ .arg("--diff")
+ .arg("--plain")
+ .arg("--color=never")
+ .arg("--decorations=always")
+ .arg("--terminal-width=80")
+ .arg("test.txt")
+ .assert()
+ .success()
+ .get_output()
+ .stdout
+ .clone();
+
+ let stdout = std::str::from_utf8(&output).expect("valid utf-8");
+
+ // Grid lines use box-drawing characters
+ assert!(
+ !stdout.contains('─'),
+ "diff plain output should not contain grid lines, got: {stdout}"
+ );
+ assert!(
+ !stdout.contains('│'),
+ "diff plain output should not contain grid separators, got: {stdout}"
+ );
+
+ // Header shows "File: "
+ assert!(
+ !stdout.contains("File:"),
+ "diff plain output should not contain file header, got: {stdout}"
+ );
+}
+
+#[cfg(unix)]
+#[cfg(feature = "git")]
+#[test]
+fn plain_without_diff_still_works() {
+ let repo = setup_diff_test_repo();
+
+ // --plain without --diff should output file content with no decorations at all
+ bat()
+ .current_dir(repo.path())
+ .arg("--plain")
+ .arg("--color=never")
+ .arg("--decorations=always")
+ .arg("test.txt")
+ .assert()
+ .success()
+ .stdout("line 1\nline 2 modified\nline 3\nline 4 added\n");
+}
From fc94a0ec49dcea3d5c022d38299f82b1f894f844 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?=
Date: Thu, 19 Mar 2026 13:26:14 +0300
Subject: [PATCH 012/130] fix: account for caret notation width in text
wrapping
Control characters displayed in caret notation (e.g. ^@ for NUL)
occupy 2 terminal columns, but the width calculation treated them
as 0-width. Add a char_width() helper that returns 2 for control
characters, fixing incorrect line wrapping with --binary=as-text.
Fixes #3631
---
CHANGELOG.md | 1 +
src/printer.rs | 18 ++++++++++++-----
.../examples/regression_tests/issue_3631.txt | Bin 0 -> 24 bytes
tests/integration_tests.rs | 19 ++++++++++++++++++
4 files changed, 33 insertions(+), 5 deletions(-)
create mode 100644 tests/examples/regression_tests/issue_3631.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7de41716..34bd719e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
## Bugfixes
+- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3631 (@eyupcanakman)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
diff --git a/src/printer.rs b/src/printer.rs
index 6a57fb62..c58c914d 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -37,6 +37,16 @@ use crate::wrapping::WrappingMode;
use crate::BinaryBehavior;
use crate::StripAnsiMode;
+// Return the displayed width of a character.
+//
+// Control characters (0x00..=0x1F and 0x7F) are rendered by the terminal
+// in caret notation (e.g. ^@, ^A, ..., ^?), which occupies two columns.
+// UnicodeWidthChar::width() returns None for these, so we map them to 2
+// here instead of the previous default of 0.
+fn char_width(c: char) -> usize {
+ c.width().unwrap_or(if c.is_control() { 2 } else { 0 })
+}
+
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
raw_sequence: "\x1B[4m",
parameters: "4",
@@ -793,7 +803,7 @@ impl Printer for InteractivePrinter<'_> {
for c in text.chars() {
// calculate the displayed width for next character
- let cw = c.width().unwrap_or(0);
+ let cw = char_width(c);
current_width += cw;
// Track whitespace positions for word wrapping.
@@ -868,10 +878,8 @@ impl Printer for InteractivePrinter<'_> {
if let Some(rs) = rest_start {
// Word wrap: carry remainder to next line.
let remainder = line_buf[rs..].to_string();
- let rem_width: usize = remainder
- .chars()
- .map(|ch| ch.width().unwrap_or(0))
- .sum();
+ let rem_width: usize =
+ remainder.chars().map(char_width).sum();
line_buf.clear();
line_buf.push_str(&remainder);
current_width = rem_width + cw;
diff --git a/tests/examples/regression_tests/issue_3631.txt b/tests/examples/regression_tests/issue_3631.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8449691fdeab44ee83cf8b8059c2aff05c2e76b5
GIT binary patch
literal 24
OcmZQzzyz*-E?fWtr~u*s
literal 0
HcmV?d00001
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index cfbad253..c6ea83e9 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -2681,6 +2681,25 @@ fn binary_as_text() {
.stderr("");
}
+#[test]
+fn binary_as_text_control_char_width() {
+ // Control characters are displayed as caret notation (e.g. ^@) by the
+ // terminal, occupying 2 columns each. With 20 NUL bytes (40 columns) +
+ // "END" (3 columns) = 43 columns, wrapping at terminal width 40 must
+ // produce 2 lines, not 1. See #3631.
+ bat()
+ .arg("--binary=as-text")
+ .arg("--wrap=character")
+ .arg("--terminal-width=40")
+ .arg("--decorations=always")
+ .arg("--style=plain")
+ .arg("--color=never")
+ .arg("regression_tests/issue_3631.txt")
+ .assert()
+ .success()
+ .stdout(predicate::function(|s: &str| s.lines().count() == 2));
+}
+
#[test]
fn no_strip_overstrike_for_plain_text() {
// Overstrike is preserved for plain text files (no syntax highlighting)
From 1f540752ef21f64aafa46007bb2bf7f328ad5230 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?=
Date: Thu, 19 Mar 2026 19:24:26 +0300
Subject: [PATCH 013/130] fix: reference PR number in CHANGELOG entry
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34bd719e..180f9ad0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
## Bugfixes
-- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3631 (@eyupcanakman)
+- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
From 3767f15c2abf07ef1edfa499fa27cfff03977393 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Fri, 20 Mar 2026 23:11:23 +0200
Subject: [PATCH 014/130] improvements from PR review
fix documentation about syntax mappings
---
src/syntax_mapping/builtins/README.md | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/syntax_mapping/builtins/README.md b/src/syntax_mapping/builtins/README.md
index 7152fd33..220e45df 100644
--- a/src/syntax_mapping/builtins/README.md
+++ b/src/syntax_mapping/builtins/README.md
@@ -20,12 +20,10 @@ syntax mappings defined by all TOML files, and embed them into the binary.
## File syntax
-Each TOML file should contain a single section named `mappings` and/or a single
-section named `case_sensitive_mappings`, with each of its keys being a language
+Each TOML file should contain a single section named `mappings`, with each of its keys being a language
identifier (first column of `bat -L`; also referred to as "target").
-The value of each key should be an array of strings, with each item being a glob
-matcher. We will call each of these items a "rule".
+The value of each key should be an array of "rules". The rules are expected to be objects with a `glob` string and a `case_sensitive` boolean. For simplification, a rule can be just a glob string, which is shorthand for the default case insensitive mode.
For example, if `foo-application` uses both TOML and YAML configuration files,
we could write something like this:
@@ -98,6 +96,15 @@ like this:
]
```
+### Case sensitivity
+
+By default, all glob patterns are matched case-insensitively. To match a pattern case-sensitively, use the object form of the rule with the `case_sensitive` option:
+
+```toml
+[mappings]
+"Python" = [{ glob = "BUILD", case_sensitive = true }]
+```
+
## Ordering
At compile time, all TOML files applicable to the target are processed in
From 7a6f442c8666bcda1649786ee48256f09b21363e Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Fri, 20 Mar 2026 23:35:27 +0200
Subject: [PATCH 015/130] improvements from PR review
- prefer to use `Default::default` because it's semantically clearer why we've chosen to use a particular value
---
build/syntax_mapping.rs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index 11d778e3..bc4601fc 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -50,9 +50,10 @@ impl ToTokens for MappingTarget {
/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
///
/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
enum Case {
Sensitive,
+ #[default]
Insensitive,
}
impl ToTokens for Case {
@@ -65,7 +66,7 @@ impl ToTokens for Case {
}
}
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
/// A single matcher.
///
/// Codegen converts this into a `Lazy>`.
From a19593b383f208106d019cbca3546cb0748eb377 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Fri, 20 Mar 2026 23:53:30 +0200
Subject: [PATCH 016/130] improvements from PR review
1. Added `Deserialize` derive and `#[serde(try_from = "RawMatcher")]` to the `Matcher` struct. With `try_from`, serde generates a `Deserialize` impl that first deserializes into `RawMatcher`, then calls `TryFrom for Matcher`. It never attempts to deserialize `Matcher`'s fields directly, so `Case` and `MatcherSegment` don't need `Deserialize` impls.
2. Replaced the manual `impl<'de> serde::Deserialize<'de> for Matcher` with a standard `impl TryFrom for Matcher`. The logic is identical - the conversion is fallible because `Matcher::from_str` returns `Result<_, anyhow::Error>`, so `try_from` (not `from`) is the correct choice, avoiding any panics.
The net effect: same behavior, same error handling, but using the idiomatic serde pattern instead of a manual deserializer impl. The `RawMatcher` intermediate type and its `#[serde(untagged)]` derive remain unchanged.
---
build/syntax_mapping.rs | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index bc4601fc..c7133be9 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -66,7 +66,8 @@ impl ToTokens for Case {
}
}
-#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize)]
+#[serde(try_from = "RawMatcher")]
/// A single matcher.
///
/// Codegen converts this into a `Lazy>`.
@@ -161,16 +162,17 @@ enum RawMatcher {
},
}
-impl<'de> serde::Deserialize<'de> for Matcher {
- fn deserialize>(deserializer: D) -> Result {
- let raw = RawMatcher::deserialize(deserializer)?;
+impl TryFrom for Matcher {
+ type Error = anyhow::Error;
+
+ fn try_from(raw: RawMatcher) -> Result {
match raw {
- RawMatcher::Simple(s) => Matcher::from_str(&s).map_err(serde::de::Error::custom),
+ RawMatcher::Simple(s) => Matcher::from_str(&s),
RawMatcher::Full {
glob,
case_sensitive,
} => {
- let mut matcher = Matcher::from_str(&glob).map_err(serde::de::Error::custom)?;
+ let mut matcher = Matcher::from_str(&glob)?;
matcher.case = if case_sensitive {
Case::Sensitive
} else {
From e60875ac125f6698775679fa9a8beb495299b4e5 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 20 Mar 2026 15:27:10 -0700
Subject: [PATCH 017/130] fix: gate Changes component on git feature flag
When bat is built without the `git` feature, `StyleComponent::Changes`
is not available. Add `#[cfg(feature = "git")]` guard so `--diff --plain`
works in both configurations.
Co-Authored-By: Claude Opus 4.6
---
src/bin/bat/app.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs
index a580ff02..f95bbb91 100644
--- a/src/bin/bat/app.rs
+++ b/src/bin/bat/app.rs
@@ -596,6 +596,7 @@ impl App {
if self.matches.try_contains_id("diff").unwrap_or_default()
&& self.matches.get_flag("diff")
{
+ #[cfg(feature = "git")]
components.insert(StyleComponent::Changes);
components.insert(StyleComponent::Snip);
}
From e86797fbf4acf3685f96539e9d3118f86186217d Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 20 Mar 2026 15:46:54 -0700
Subject: [PATCH 018/130] style: auto-format integration tests
Co-Authored-By: Claude Opus 4.6
---
tests/integration_tests.rs | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 9a8a7f41..b0165126 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -3993,8 +3993,7 @@ fn setup_diff_test_repo() -> tempfile::TempDir {
.output()
.expect("git config name");
- std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n")
- .expect("can write test file");
+ std::fs::write(repo.join("test.txt"), "line 1\nline 2\nline 3\n").expect("can write test file");
Command::new("git")
.args(["add", "test.txt"])
@@ -4009,8 +4008,11 @@ fn setup_diff_test_repo() -> tempfile::TempDir {
.expect("git commit");
// Modify the file so --diff has something to show
- std::fs::write(repo.join("test.txt"), "line 1\nline 2 modified\nline 3\nline 4 added\n")
- .expect("can write modified test file");
+ std::fs::write(
+ repo.join("test.txt"),
+ "line 1\nline 2 modified\nline 3\nline 4 added\n",
+ )
+ .expect("can write modified test file");
dir
}
From 0b4c886efc31d0adf8a99303006620f289146375 Mon Sep 17 00:00:00 2001
From: Sungjoon Moon
Date: Thu, 25 Dec 2025 20:19:30 +0000
Subject: [PATCH 019/130] ci: Use git version of cross for better target
support
---
.github/workflows/CICD.yml | 4 +---
CHANGELOG.md | 1 +
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml
index e1b82424..4c3db0c8 100644
--- a/.github/workflows/CICD.yml
+++ b/.github/workflows/CICD.yml
@@ -195,9 +195,7 @@ jobs:
- name: Install cross
if: matrix.job.use-cross
- uses: taiki-e/install-action@v2
- with:
- tool: cross
+ run: cargo install cross --git https://github.com/cross-rs/cross --rev 588b3c99db52b5a9c5906fab96cfadcf1bde7863
- name: Overwrite build command env variable
if: matrix.job.use-cross
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf2c2f84..08b116ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
## Other
+- Use git version of cross. See #3533 (@OctopusET)
- Bump MSRV to 1.88, update `time` crate to 0.3.47 to fix RUSTSEC-2026-0009, see #3581 (@NORMAL-EX)
From ca3ef28d56a2b010f2ddf8fa01be64aec905ab4f Mon Sep 17 00:00:00 2001
From: Sim-hu
Date: Tue, 24 Mar 2026 19:20:18 +0900
Subject: [PATCH 020/130] fix: use correct Debian architecture name for i686
.deb package
The i686 .deb package declared its architecture as "i686", which
Debian does not recognize. Debian uses "i386" for 32-bit x86,
matching the convention already used by the other targets (arm64,
armhf, amd64).
Closes #3611
---
.github/workflows/CICD.yml | 2 +-
CHANGELOG.md | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml
index 4c3db0c8..f881cada 100644
--- a/.github/workflows/CICD.yml
+++ b/.github/workflows/CICD.yml
@@ -166,7 +166,7 @@ jobs:
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest , dpkg_arch: armhf, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-latest , dpkg_arch: musl-linux-armhf, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2025 , }
- - { target: i686-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: i686, use-cross: true }
+ - { target: i686-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: i386, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: musl-linux-i686, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-15-intel, }
- { target: aarch64-apple-darwin , os: macos-latest , }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08b116ff..b9611731 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
+- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
From b8d462ba87f4ec4c5e3d6862f2dfd5865b97b481 Mon Sep 17 00:00:00 2001
From: Sim-hu
Date: Tue, 24 Mar 2026 19:24:36 +0900
Subject: [PATCH 021/130] fix: add PR number and author to changelog entry
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9611731..217d3baa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
-- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611
+- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
From 87e043b91ce47f95829ad74ac06e04d0a163dccf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 22 Mar 2026 18:46:46 +0000
Subject: [PATCH 022/130] Add Home/End key bindings to builtin minus pager
Co-authored-by: keith-hall <11882719+keith-hall@users.noreply.github.com>
---
src/output.rs | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/output.rs b/src/output.rs
index 0205f48e..8054be46 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -23,6 +23,18 @@ pub struct BuiltinPager {
impl BuiltinPager {
fn new() -> Self {
let pager = minus::Pager::new();
+
+ let mut input_register = minus::input::HashedEventRegister::default();
+ input_register.add_key_events(&["home"], |_, _| {
+ minus::input::InputEvent::UpdateUpperMark(0)
+ });
+ input_register.add_key_events(&["end"], |_, _| {
+ minus::input::InputEvent::UpdateUpperMark(usize::MAX)
+ });
+ pager
+ .set_input_classifier(Box::new(input_register))
+ .expect("failed to set input classifier on newly created pager");
+
let handle = {
let pager = pager.clone();
Some(spawn(move || {
From eef71074070adebb33cc3144db0bbf154c669927 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Tue, 24 Mar 2026 21:38:07 +0200
Subject: [PATCH 023/130] update changelog
---
CHANGELOG.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08b116ff..52d732f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,5 @@
# unreleased
-- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
-- Fixed syntax tests path, see #3610 (@foxfromworld)
## Features
@@ -27,11 +25,13 @@
- `--help` now correctly honors `--pager=builtin`. See #3516 (@keith-hall)
- `--help` now correctly honors custom themes. See #3524 (@keith-hall)
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
+- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
+- Fixed syntax tests path, see #3610 (@foxfromworld)
## Other
- Use git version of cross. See #3533 (@OctopusET)
-
- Bump MSRV to 1.88, update `time` crate to 0.3.47 to fix RUSTSEC-2026-0009, see #3581 (@NORMAL-EX)
+- Allow home and end keys to be used with builtin pager, see #3651 (@keith-hall)
## Syntaxes
From 3e789f5241dfedc789de7a8b7e2bd704858514d2 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Wed, 25 Mar 2026 14:49:50 +0800
Subject: [PATCH 024/130] Imprv: cleanup matcher glob parsing logic
- Data flow is now strictly linear from `RawMatcher` to `Matcher`
- I've also hoisted `RawMatcher` in front of `Matcher` to signal this
- Re-type `RawMatcher.case_sensitive` from `bool` to `Option`
- This moves all parser logic away from `RawMatcher`, making it a more faithful representation of the data
- Favour default consts in `Matcher::try_from` to `Default` impl on `Case`
- Because the default choice of casing is a design decision of the logic, not an intrinsic property of the type
---
build/syntax_mapping.rs | 153 ++++++++++++++++++++--------------------
1 file changed, 75 insertions(+), 78 deletions(-)
diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs
index c7133be9..b0db01eb 100644
--- a/build/syntax_mapping.rs
+++ b/build/syntax_mapping.rs
@@ -47,13 +47,24 @@ impl ToTokens for MappingTarget {
}
}
+/// Helper type for deserializing a `Matcher` from either a plain string or a
+/// `{ glob = "...", case_sensitive = true }` struct.
+#[derive(Deserialize)]
+#[serde(untagged)]
+enum RawMatcher {
+ Simple(String),
+ Full {
+ glob: String,
+ case_sensitive: Option,
+ },
+}
+
/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
///
/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum Case {
Sensitive,
- #[default]
Insensitive,
}
impl ToTokens for Case {
@@ -66,11 +77,11 @@ impl ToTokens for Case {
}
}
-#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize)]
-#[serde(try_from = "RawMatcher")]
/// A single matcher.
///
/// Codegen converts this into a `Lazy>`.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
+#[serde(try_from = "RawMatcher")]
struct Matcher {
segments: Vec,
/// Whether the glob pattern should be matched case-sensitively.
@@ -78,7 +89,7 @@ struct Matcher {
/// Defaults to `Case::Insensitive` for backwards compatibility.
case: Case,
}
-/// Parse a matcher.
+/// Parse the glob pattern of a matcher.
///
/// Note that this implementation is rather strict: it will greedily interpret
/// every valid environment variable replacement as such, then immediately
@@ -92,93 +103,79 @@ struct Matcher {
///
/// Revision history:
/// - 2024-02-20: allow `{` and `}` (glob brace expansion)
-impl FromStr for Matcher {
- type Err = anyhow::Error;
- fn from_str(s: &str) -> Result {
- use MatcherSegment as Seg;
- static VAR_REGEX: Lazy = Lazy::new(|| Regex::new(r"\$\{([\w\d_]+)\}").unwrap());
+fn parse_glob(s: &str) -> Result, anyhow::Error> {
+ use MatcherSegment as Seg;
+ static VAR_REGEX: Lazy = Lazy::new(|| Regex::new(r"\$\{([\w\d_]+)\}").unwrap());
- let mut segments = vec![];
- let mut text_start = 0;
- for capture in VAR_REGEX.captures_iter(s) {
- let match_0 = capture.get(0).unwrap();
+ let mut segments = vec![];
+ let mut text_start = 0;
+ for capture in VAR_REGEX.captures_iter(s) {
+ let match_0 = capture.get(0).unwrap();
- // text before this var
- let text_end = match_0.start();
- segments.push(Seg::Text(s[text_start..text_end].into()));
- text_start = match_0.end();
+ // text before this var
+ let text_end = match_0.start();
+ segments.push(Seg::Text(s[text_start..text_end].into()));
+ text_start = match_0.end();
- // this var
- segments.push(Seg::Env(capture.get(1).unwrap().as_str().into()));
- }
- // possible trailing text
- segments.push(Seg::Text(s[text_start..].into()));
-
- // cleanup empty text segments
- let non_empty_segments = segments
- .into_iter()
- .filter(|seg| seg.text().map(|t| !t.is_empty()).unwrap_or(true))
- .collect_vec();
-
- // sanity check
- if non_empty_segments
- .windows(2)
- .any(|segs| segs[0].is_text() && segs[1].is_text())
- {
- unreachable!("Parsed into consecutive text segments: {non_empty_segments:?}");
- }
-
- // guard empty case
- if non_empty_segments.is_empty() {
- bail!(r#"Parsed an empty matcher: "{s}""#);
- }
-
- // guard variable syntax leftover fragments
- if non_empty_segments
- .iter()
- .filter_map(Seg::text)
- .any(|t| t.contains('$'))
- {
- bail!(r#"Invalid matcher: "{s}""#);
- }
-
- Ok(Self {
- segments: non_empty_segments,
- case: Case::Insensitive,
- })
+ // this var
+ segments.push(Seg::Env(capture.get(1).unwrap().as_str().into()));
}
-}
+ // possible trailing text
+ segments.push(Seg::Text(s[text_start..].into()));
-/// Helper type for deserializing a `Matcher` from either a plain string or a
-/// `{ glob = "...", case_sensitive = true }` struct.
-#[derive(Deserialize)]
-#[serde(untagged)]
-enum RawMatcher {
- Simple(String),
- Full {
- glob: String,
- #[serde(default)]
- case_sensitive: bool,
- },
-}
+ // cleanup empty text segments
+ let non_empty_segments = segments
+ .into_iter()
+ .filter(|seg| seg.text().map(|t| !t.is_empty()).unwrap_or(true))
+ .collect_vec();
+ // sanity check
+ if non_empty_segments
+ .windows(2)
+ .any(|segs| segs[0].is_text() && segs[1].is_text())
+ {
+ unreachable!("Parsed into consecutive text segments: {non_empty_segments:?}");
+ }
+
+ // guard empty case
+ if non_empty_segments.is_empty() {
+ bail!(r#"Parsed an empty matcher: "{s}""#);
+ }
+
+ // guard variable syntax leftover fragments
+ if non_empty_segments
+ .iter()
+ .filter_map(Seg::text)
+ .any(|t| t.contains('$'))
+ {
+ bail!(r#"Invalid matcher: "{s}""#);
+ }
+
+ Ok(non_empty_segments)
+}
impl TryFrom for Matcher {
type Error = anyhow::Error;
-
fn try_from(raw: RawMatcher) -> Result {
- match raw {
- RawMatcher::Simple(s) => Matcher::from_str(&s),
+ const DEFAULT_CASE: Case = Case::Insensitive;
+ match &raw {
+ RawMatcher::Simple(s) => {
+ let segments = parse_glob(s)?;
+ Ok(Self {
+ segments,
+ case: DEFAULT_CASE,
+ })
+ }
RawMatcher::Full {
glob,
case_sensitive,
} => {
- let mut matcher = Matcher::from_str(&glob)?;
- matcher.case = if case_sensitive {
- Case::Sensitive
- } else {
- Case::Insensitive
+ let segments = parse_glob(glob)?;
+ let case = match case_sensitive {
+ None => DEFAULT_CASE,
+ Some(false) => Case::Insensitive,
+ Some(true) => Case::Sensitive,
};
- Ok(matcher)
+ Ok(Self { segments, case })
}
}
}
From b511b928f440ed1f5fcba305a47d93bd68f379a0 Mon Sep 17 00:00:00 2001
From: cyqsimon <28627918+cyqsimon@users.noreply.github.com>
Date: Wed, 25 Mar 2026 15:00:46 +0800
Subject: [PATCH 025/130] Write changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecd7770b..251e699d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@
- Use git version of cross. See #3533 (@OctopusET)
- Bump MSRV to 1.88, update `time` crate to 0.3.47 to fix RUSTSEC-2026-0009, see #3581 (@NORMAL-EX)
- Allow home and end keys to be used with builtin pager, see #3651 (@keith-hall)
+- Builtin syntax mapping: cleanup matcher glob parsing logic #3652 (@cyqsimon)
## Syntaxes
From 7ff1fb3278b756e73a71911d1ae9a702af1c221f Mon Sep 17 00:00:00 2001
From: Weixie Cui
Date: Thu, 26 Mar 2026 11:10:58 +0800
Subject: [PATCH 026/130] fix: warn when $LESSCLOSE fails, not when it succeeds
The Drop cleanup for Preprocessed inverted the exit-status check, so bat
emitted a warning when LESSCLOSE exited successfully and stayed silent on
failure. Align the condition with the intended behavior.
---
src/lessopen.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lessopen.rs b/src/lessopen.rs
index 966bc2c0..81ae3cff 100644
--- a/src/lessopen.rs
+++ b/src/lessopen.rs
@@ -261,7 +261,7 @@ impl Drop for Preprocessed {
}
};
- if lessclose_output.status.success() {
+ if !lessclose_output.status.success() {
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
};
}
From 5c3b8040dbcbe6c39b84746ceab944fd97db859d Mon Sep 17 00:00:00 2001
From: Weixie Cui
Date: Thu, 26 Mar 2026 11:11:19 +0800
Subject: [PATCH 027/130] fix: add changelog entry for LESSCLOSE warning fix
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 251e699d..99831cf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
+- Fix inverted `$LESSCLOSE` warning so bat warns on nonzero exit, not on success. See #3654 (@cuiweixie)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
From 89f3d2d31afe5487c89a7d14b48717b56758f243 Mon Sep 17 00:00:00 2001
From: Claw Explorer
Date: Thu, 26 Mar 2026 12:18:52 -0400
Subject: [PATCH 028/130] docs: add instructions for removing fish help
abbreviations
Add documentation showing how to erase the fish abbreviations for
--help and -h, since the dash-prefixed names make removal non-obvious.
The key is using -- before the abbreviation name to prevent fish from
interpreting it as a flag.
Closes #3536
---
CHANGELOG.md | 4 ++++
README.md | 9 +++++++++
2 files changed, 13 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 251e699d..cd21680c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# unreleased
+## Other
+
+- Add instructions for removing fish help abbreviations to README, see #3655 (@claw-explorer). Closes #3536
+
## Features
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
diff --git a/README.md b/README.md
index d5a68440..140c3022 100644
--- a/README.md
+++ b/README.md
@@ -244,6 +244,15 @@ abbr -a --position anywhere -- --help '--help | bat -plhelp'
abbr -a --position anywhere -- -h '-h | bat -plhelp'
```
+To remove these abbreviations later:
+
+```fish
+abbr -e -- --help
+abbr -e -- -h
+```
+
+The `--` before the abbreviation name is required because `--help` and `-h` start with dashes, which would otherwise be interpreted as flags to `abbr` itself.
+
This way, you can keep on using `cp --help`, but get colorized help pages.
Be aware that in some cases, `-h` may not be a shorthand of `--help` (for example with `ls`). In cases where you need to use `-h`
From 652489251b5e8eeefea0c339adf293e5f8341db7 Mon Sep 17 00:00:00 2001
From: Claw Explorer
Date: Sat, 28 Mar 2026 16:34:28 -0400
Subject: [PATCH 029/130] docs: move removal instructions into a TIP callout
Address review feedback from @keith-hall: wrap the abbreviation
removal instructions in a GitHub callout (> [!TIP]) to visually
separate them from the main setup flow. Also reorder so the
'This way, you can keep on using...' sentence follows directly
after the abbreviation creation, improving readability.
---
README.md | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 140c3022..d6ef3381 100644
--- a/README.md
+++ b/README.md
@@ -244,17 +244,16 @@ abbr -a --position anywhere -- --help '--help | bat -plhelp'
abbr -a --position anywhere -- -h '-h | bat -plhelp'
```
-To remove these abbreviations later:
-
-```fish
-abbr -e -- --help
-abbr -e -- -h
-```
-
-The `--` before the abbreviation name is required because `--help` and `-h` start with dashes, which would otherwise be interpreted as flags to `abbr` itself.
-
This way, you can keep on using `cp --help`, but get colorized help pages.
+> [!TIP]
+> To remove these abbreviations later, run:
+> ```fish
+> abbr -e -- --help
+> abbr -e -- -h
+> ```
+> The `--` before the abbreviation name is required because `--help` and `-h` start with dashes, which would otherwise be interpreted as flags to `abbr` itself.
+
Be aware that in some cases, `-h` may not be a shorthand of `--help` (for example with `ls`). In cases where you need to use `-h`
as a command argument you can prepend `\` to the argument (eg. `ls \-h`) to escape the aliasing defined above.
From abab6533e4d1ff26c1a4f706b8f54235683bd997 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Tue, 31 Mar 2026 22:22:31 +0300
Subject: [PATCH 030/130] Update --diagnostic output with BAT_THEME_LIGHT/DARK
env vars
---
src/bin/bat/main.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index 7ca08b9d..5a771cd6 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -307,6 +307,8 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
"BAT_STYLE",
"BAT_TABS",
"BAT_THEME",
+ bat::theme::env::BAT_THEME_DARK,
+ bat::theme::env::BAT_THEME_LIGHT,
"COLORTERM",
"LANG",
"LC_ALL",
From ba223289ce7325ddb0c95d22b4bf74e2fb7ee3e5 Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Tue, 31 Mar 2026 22:23:10 +0300
Subject: [PATCH 031/130] Update --diagnostic output with detected terminal
color scheme type
---
src/bin/bat/main.rs | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index 5a771cd6..3cdf0531 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -285,7 +285,29 @@ fn run_controller(inputs: Vec , config: &Config, cache_dir: &Path) -> Resu
#[cfg(feature = "bugreport")]
fn invoke_bugreport(app: &App, cache_dir: &Path) {
- use bugreport::{bugreport, collector::*, format::Markdown};
+ use bugreport::{bugreport, collector::*, format::Markdown, report::ReportEntry};
+
+ struct ColorSchemeCollector;
+
+ impl Collector for ColorSchemeCollector {
+ fn description(&self) -> &str {
+ "Detected terminal color scheme"
+ }
+
+ fn collect(
+ &mut self,
+ _: &bugreport::CrateInfo,
+ ) -> std::result::Result {
+ let color_scheme =
+ bat::theme::color_scheme(bat::theme::DetectColorScheme::Always);
+ let text = match color_scheme {
+ Some(bat::theme::ColorScheme::Dark) => "dark",
+ Some(bat::theme::ColorScheme::Light) => "light",
+ None => "not detected",
+ };
+ Ok(ReportEntry::Text(text.to_string()))
+ }
+ }
let pager = bat::config::get_pager_executable(
app.matches.get_one::("pager").map(|s| s.as_str()),
)
@@ -328,6 +350,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
custom_assets_metadata,
))
.info(DirectoryEntries::new("Custom assets", cache_dir))
+ .info(ColorSchemeCollector)
.info(CompileTimeInformation::default());
#[cfg(feature = "paging")]
From 3e4169b8c3cc3b5d5824ff821279c5ce6872467a Mon Sep 17 00:00:00 2001
From: Keith Hall
Date: Tue, 31 Mar 2026 22:35:21 +0300
Subject: [PATCH 032/130] cargo fmt
---
src/bin/bat/main.rs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index 3cdf0531..a4a2c176 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -298,8 +298,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
&mut self,
_: &bugreport::CrateInfo,
) -> std::result::Result {
- let color_scheme =
- bat::theme::color_scheme(bat::theme::DetectColorScheme::Always);
+ let color_scheme = bat::theme::color_scheme(bat::theme::DetectColorScheme::Always);
let text = match color_scheme {
Some(bat::theme::ColorScheme::Dark) => "dark",
Some(bat::theme::ColorScheme::Light) => "light",
From 31b0a357190eec0b9df23b5f6ee2115402ab3f78 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2026 02:03:59 +0000
Subject: [PATCH 033/130] build(deps): bump unicode-segmentation from 1.12.0 to
1.13.2
Bumps [unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation) from 1.12.0 to 1.13.2.
- [Commits](https://github.com/unicode-rs/unicode-segmentation/compare/v1.12.0...v1.13.2)
---
updated-dependencies:
- dependency-name: unicode-segmentation
dependency-version: 1.13.2
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 4 ++--
Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 2e777a2e..baedc438 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1808,9 +1808,9 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
-version = "1.12.0"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-width"
diff --git a/Cargo.toml b/Cargo.toml
index 6c783336..4a8ecdc9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73,7 +73,7 @@ bytesize = { version = "2.3.1" }
encoding_rs = "0.8.35"
execute = { version = "0.2.15", optional = true }
terminal-colorsaurus = "1.0"
-unicode-segmentation = "1.12.0"
+unicode-segmentation = "1.13.2"
itertools = "0.14.0"
[dependencies.git2]
From f820ad1050654b36d1244f06c534dc9f73665087 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2026 02:41:02 +0000
Subject: [PATCH 034/130] build(deps): bump unicode-width from 0.2.1 to 0.2.2
Bumps [unicode-width](https://github.com/unicode-rs/unicode-width) from 0.2.1 to 0.2.2.
- [Commits](https://github.com/unicode-rs/unicode-width/compare/v0.2.1...v0.2.2)
---
updated-dependencies:
- dependency-name: unicode-width
dependency-version: 0.2.2
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 4 ++--
Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index baedc438..db7f3f36 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1814,9 +1814,9 @@ checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-width"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unsafe-libyaml"
diff --git a/Cargo.toml b/Cargo.toml
index 4a8ecdc9..99a97871 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,7 +56,7 @@ minus = { version = "5.6", optional = true, features = [
"dynamic_output",
"search",
] }
-unicode-width = "0.2.1"
+unicode-width = "0.2.2"
globset = "0.4"
serde = "1.0"
serde_derive = "1.0"
From de9ca4ef7b5e4a9b949684c2e0cc6872ec41a8eb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2026 03:13:41 +0000
Subject: [PATCH 035/130] build(deps): bump toml from 0.9.8 to 1.1.1+spec-1.1.0
Bumps [toml](https://github.com/toml-rs/toml) from 0.9.8 to 1.1.1+spec-1.1.0.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.9.8...toml-v1.1.1)
---
updated-dependencies:
- dependency-name: toml
dependency-version: 1.1.1+spec-1.1.0
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 24 ++++++++++++------------
Cargo.toml | 2 +-
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index db7f3f36..f7c76d47 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1406,9 +1406,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
-version = "1.0.3"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
@@ -1757,9 +1757,9 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.9.8"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
+checksum = "994b95d9e7bae62b34bab0e2a4510b801fa466066a6a8b2b57361fa1eba068ee"
dependencies = [
"indexmap",
"serde_core",
@@ -1772,27 +1772,27 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.7.3"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
-version = "1.0.4"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
+checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
-version = "1.0.4"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
+checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "typenum"
@@ -2186,9 +2186,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
-version = "0.7.14"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
[[package]]
name = "wit-bindgen-rt"
diff --git a/Cargo.toml b/Cargo.toml
index 99a97871..68add205 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -119,7 +119,7 @@ serde = "1.0"
serde_derive = "1.0"
serde_with = { version = "3.17.0", default-features = false, features = ["macros"] }
syn = { version = "2.0.104", features = ["full"] }
-toml = { version = "0.9.8", features = ["preserve_order"] }
+toml = { version = "1.1.1", features = ["preserve_order"] }
walkdir = "2.5"
[build-dependencies.clap]
From 57e99e9bcdcd4d58946c687d8b7d3684d5072890 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2026 03:38:08 +0000
Subject: [PATCH 036/130] build(deps): bump tempfile from 3.23.0 to 3.27.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.23.0 to 3.27.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.23.0...v3.27.0)
---
updated-dependencies:
- dependency-name: tempfile
dependency-version: 3.27.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 16 ++++++++--------
Cargo.toml | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f7c76d47..62f29a10 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -951,9 +951,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
@@ -1324,14 +1324,14 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.1.2"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
- "linux-raw-sys 0.11.0",
+ "linux-raw-sys 0.12.1",
"windows-sys 0.61.2",
]
@@ -1603,14 +1603,14 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.23.0"
+version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
- "rustix 1.1.2",
+ "rustix 1.1.4",
"windows-sys 0.61.2",
]
diff --git a/Cargo.toml b/Cargo.toml
index 68add205..4d85a6e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -100,7 +100,7 @@ expect-test = "1.5.0"
serial_test = { version = "2.0.0", default-features = false }
predicates = "3.1.3"
wait-timeout = "0.2.1"
-tempfile = "3.23.0"
+tempfile = "3.27.0"
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(unix)'.dev-dependencies]
From 956f0aeb08f8e4cefa59279e15eeb5be1ed41721 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 1 Apr 2026 03:54:15 +0000
Subject: [PATCH 037/130] build(deps): bump quote from 1.0.40 to 1.0.45
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.40 to 1.0.45.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.40...1.0.45)
---
updated-dependencies:
- dependency-name: quote
dependency-version: 1.0.45
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
Cargo.lock | 4 ++--
Cargo.toml | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 62f29a10..202a5295 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1235,9 +1235,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.40"
+version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
diff --git a/Cargo.toml b/Cargo.toml
index 4d85a6e3..b63c4e83 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -113,7 +113,7 @@ itertools = "0.14.0"
once_cell = "1.20"
prettyplease = "0.2.37"
proc-macro2 = "1.0.106"
-quote = "1.0.40"
+quote = "1.0.45"
regex = "1.12.2"
serde = "1.0"
serde_derive = "1.0"
From c64b6761feded76d519c91dda256e0779a4842b4 Mon Sep 17 00:00:00 2001
From: Lucas Trzesniewski
Date: Tue, 7 Apr 2026 23:04:24 +0200
Subject: [PATCH 038/130] Add .NET slnx extension
This is the new XML-based format for solution files.
---
CHANGELOG.md | 1 +
src/syntax_mapping/builtins/common/50-dotnet-xml.toml | 2 +-
tests/syntax-tests/highlighted/XML/solution.slnx | 7 +++++++
tests/syntax-tests/source/XML/solution.slnx | 7 +++++++
4 files changed, 16 insertions(+), 1 deletion(-)
create mode 100644 tests/syntax-tests/highlighted/XML/solution.slnx
create mode 100644 tests/syntax-tests/source/XML/solution.slnx
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd21680c..c67cf148 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
## Other
- Add instructions for removing fish help abbreviations to README, see #3655 (@claw-explorer). Closes #3536
+- Add .NET slnx extension, see #3682 (@ltrzesniewski)
## Features
diff --git a/src/syntax_mapping/builtins/common/50-dotnet-xml.toml b/src/syntax_mapping/builtins/common/50-dotnet-xml.toml
index 1e3a860a..70319f83 100644
--- a/src/syntax_mapping/builtins/common/50-dotnet-xml.toml
+++ b/src/syntax_mapping/builtins/common/50-dotnet-xml.toml
@@ -1,2 +1,2 @@
[mappings]
-"XML" = ["*.csproj", "*.vbproj", "*.props", "*.targets"]
+"XML" = ["*.csproj", "*.vbproj", "*.props", "*.targets", "*.slnx"]
diff --git a/tests/syntax-tests/highlighted/XML/solution.slnx b/tests/syntax-tests/highlighted/XML/solution.slnx
new file mode 100644
index 00000000..7946e72b
--- /dev/null
+++ b/tests/syntax-tests/highlighted/XML/solution.slnx
@@ -0,0 +1,7 @@
+[38;2;255;255;255m<[0m[38;2;249;38;114mSolution[0m[38;2;255;255;255m>[0m
+[38;2;248;248;242m [0m[38;2;255;255;255m<[0m[38;2;249;38;114mFolder[0m[38;2;248;248;242m [0m[38;2;166;226;46mName[0m[38;2;248;248;242m=[0m[38;2;230;219;116m"[0m[38;2;230;219;116m/Build/[0m[38;2;230;219;116m"[0m[38;2;255;255;255m>[0m
+[38;2;248;248;242m [0m[38;2;255;255;255m<[0m[38;2;249;38;114mFile[0m[38;2;248;248;242m [0m[38;2;166;226;46mPath[0m[38;2;248;248;242m=[0m[38;2;230;219;116m"[0m[38;2;230;219;116mDirectory.Build.props[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;255;255;255m/>[0m
+[38;2;248;248;242m [0m[38;2;255;255;255m<[0m[38;2;249;38;114mFile[0m[38;2;248;248;242m [0m[38;2;166;226;46mPath[0m[38;2;248;248;242m=[0m[38;2;230;219;116m"[0m[38;2;230;219;116mprojectname.targets[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;255;255;255m/>[0m
+[38;2;248;248;242m [0m[38;2;255;255;255m[0m[38;2;249;38;114mFolder[0m[38;2;255;255;255m>[0m
+[38;2;248;248;242m [0m[38;2;255;255;255m<[0m[38;2;249;38;114mProject[0m[38;2;248;248;242m [0m[38;2;166;226;46mPath[0m[38;2;248;248;242m=[0m[38;2;230;219;116m"[0m[38;2;230;219;116mconsole.csproj[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;255;255;255m/>[0m
+[38;2;255;255;255m[0m[38;2;249;38;114mSolution[0m[38;2;255;255;255m>[0m
diff --git a/tests/syntax-tests/source/XML/solution.slnx b/tests/syntax-tests/source/XML/solution.slnx
new file mode 100644
index 00000000..e836bb51
--- /dev/null
+++ b/tests/syntax-tests/source/XML/solution.slnx
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
From 2459aa94047279f9d7e6290010c6e8547b870962 Mon Sep 17 00:00:00 2001
From: Asish Kumar
Date: Fri, 10 Apr 2026 14:35:33 +0000
Subject: [PATCH 039/130] Detect ZIP archives as binary content
---
CHANGELOG.md | 1 +
src/input.rs | 41 +++++++++++++++++++++++++++++++++-----
tests/integration_tests.rs | 18 +++++++++++++++++
3 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c67cf148..faf869a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
+- Treat ZIP archives as binary content based on their magic header, see #0000 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
diff --git a/src/input.rs b/src/input.rs
index 29846abe..30f13f98 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -261,11 +261,7 @@ impl<'a> InputReader<'a> {
let mut first_line = vec![];
reader.read_until(b'\n', &mut first_line).ok();
- let content_type = if first_line.is_empty() {
- None
- } else {
- Some(content_inspector::inspect(&first_line[..]))
- };
+ let content_type = inspect_content_type(&first_line);
if content_type == Some(ContentType::UTF_16LE) {
read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok();
@@ -319,6 +315,25 @@ impl<'a> InputReader<'a> {
}
}
+fn inspect_content_type(first_line: &[u8]) -> Option {
+ if first_line.is_empty() {
+ return None;
+ }
+
+ let content_type = content_inspector::inspect(first_line);
+ if content_type == ContentType::UTF_8 && has_zip_signature(first_line) {
+ Some(ContentType::BINARY)
+ } else {
+ Some(content_type)
+ }
+}
+
+fn has_zip_signature(bytes: &[u8]) -> bool {
+ [b"PK\x03\x04", b"PK\x05\x06", b"PK\x07\x08"]
+ .into_iter()
+ .any(|signature| bytes.starts_with(signature))
+}
+
fn read_utf16_line(
reader: &mut R,
buf: &mut Vec,
@@ -374,6 +389,22 @@ fn basic() {
assert!(buffer.is_empty());
}
+#[test]
+fn zip_magic_headers_are_treated_as_binary() {
+ for content in [b"PK\x03\x04hello", b"PK\x05\x06hello", b"PK\x07\x08hello"] {
+ let reader = InputReader::new(&content[..]);
+ assert_eq!(Some(ContentType::BINARY), reader.content_type);
+ }
+}
+
+#[test]
+fn non_zip_pk_prefix_is_not_treated_as_binary() {
+ assert_eq!(
+ Some(ContentType::UTF_8),
+ inspect_content_type(b"PK\x03\x03hello")
+ );
+}
+
#[test]
fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 96da50d7..d7e44301 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -2093,6 +2093,24 @@ fn header_binary() {
.stderr("");
}
+#[test]
+fn header_zip_file_is_binary() {
+ let tmp_dir = tempdir().expect("can create temporary directory");
+ let tmp_path = tmp_dir.path().join("test.zip");
+ std::fs::write(&tmp_path, b"PK\x03\x04hello").expect("can write temporary file");
+
+ bat()
+ .arg(&tmp_path)
+ .arg("--decorations=always")
+ .arg("--style=header")
+ .arg("-r=0:0")
+ .arg("--file-name=test.zip")
+ .assert()
+ .success()
+ .stdout("File: test.zip \n")
+ .stderr("");
+}
+
#[test]
fn header_full_binary() {
bat()
From b3aec318cf1fc16edefe8a90d78e3eb2f62100f8 Mon Sep 17 00:00:00 2001
From: Asish Kumar
Date: Fri, 10 Apr 2026 14:37:00 +0000
Subject: [PATCH 040/130] Update changelog entry for #3686
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index faf869a5..796aac21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,7 +19,7 @@
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
## Bugfixes
-- Treat ZIP archives as binary content based on their magic header, see #0000 (@officialasishkumar)
+- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
From a9137fab05f8204804c5cfecf480f4c8f2d7ab82 Mon Sep 17 00:00:00 2001
From: Asish Kumar
Date: Fri, 10 Apr 2026 14:35:33 +0000
Subject: [PATCH 041/130] Support BAT_WIDTH as an alias for --terminal-width
Signed-off-by: Asish Kumar
---
CHANGELOG.md | 1 +
doc/long-help.txt | 3 ++-
src/bin/bat/clap_app.rs | 8 ++++--
src/bin/bat/config.rs | 1 +
src/bin/bat/main.rs | 1 +
tests/integration_tests.rs | 52 ++++++++++++++++++++++++++++++++++++++
6 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 796aac21..0539669b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
+- Support configuring `--terminal-width` via `BAT_WIDTH`, see #0000 (@officialasishkumar)
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
- Added an initial `flake.nix` for a ready made development environment; see #3578 (@vorburger)
- Add `--quiet-empty` (`-E`) flag to suppress output when input is empty. Closes #1936, see #3563 (@NORMAL-EX)
diff --git a/doc/long-help.txt b/doc/long-help.txt
index 82878acd..7d3cc0e5 100644
--- a/doc/long-help.txt
+++ b/doc/long-help.txt
@@ -77,7 +77,8 @@ Options:
--terminal-width
Explicitly set the width of the terminal instead of determining it automatically. If
prefixed with '+' or '-', the value will be treated as an offset to the actual terminal
- width. See also: '--wrap'.
+ width. This can also be configured via the BAT_WIDTH environment variable (e.g. export
+ BAT_WIDTH="100"). See also: '--wrap'.
-n, --number
Only show line numbers, no other decorations. This is an alias for '--style=numbers'
diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs
index 3636f081..6dfdf829 100644
--- a/src/bin/bat/clap_app.rs
+++ b/src/bin/bat/clap_app.rs
@@ -240,6 +240,7 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("terminal-width")
.long("terminal-width")
+ .overrides_with("terminal-width")
.value_name("width")
.hide_short_help(true)
.allow_hyphen_values(true)
@@ -255,10 +256,13 @@ pub fn build_app(interactive_output: bool) -> Command {
})
.map_err(|e| e.to_string())
})
- .help(
+ .help("Explicitly set the width of the terminal instead of determining it automatically.")
+ .long_help(
"Explicitly set the width of the terminal instead of determining it \
automatically. If prefixed with '+' or '-', the value will be treated \
- as an offset to the actual terminal width. See also: '--wrap'.",
+ as an offset to the actual terminal width. This can also be configured \
+ via the BAT_WIDTH environment variable (e.g. export BAT_WIDTH=\"100\"). \
+ See also: '--wrap'.",
),
)
.arg(
diff --git a/src/bin/bat/config.rs b/src/bin/bat/config.rs
index 77d691c3..3e2fb72e 100644
--- a/src/bin/bat/config.rs
+++ b/src/bin/bat/config.rs
@@ -153,6 +153,7 @@ fn get_args_from_str(content: &str) -> Result, shell_words::ParseE
pub fn get_args_from_env_vars() -> Vec {
[
("--tabs", "BAT_TABS"),
+ ("--terminal-width", "BAT_WIDTH"),
("--theme", bat::theme::env::BAT_THEME),
("--theme-dark", bat::theme::env::BAT_THEME_DARK),
("--theme-light", bat::theme::env::BAT_THEME_LIGHT),
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index a4a2c176..f3fa01f4 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -328,6 +328,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
"BAT_STYLE",
"BAT_TABS",
"BAT_THEME",
+ "BAT_WIDTH",
bat::theme::env::BAT_THEME_DARK,
bat::theme::env::BAT_THEME_LIGHT,
"COLORTERM",
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index d7e44301..8ae4cfdd 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -1047,6 +1047,57 @@ fn tabs_4_arg_overrides_env_noconfig() {
);
}
+#[test]
+fn terminal_width_env_var_is_respected() {
+ let tmp_dir = tempdir().expect("can create temporary directory");
+ let tmp_path = tmp_dir.path().join("long.txt");
+ std::fs::write(
+ &tmp_path,
+ "0123456789abcdef0123456789abcdef0123456789abcdef\n",
+ )
+ .expect("can write temporary file");
+
+ bat()
+ .env("BAT_WIDTH", "20")
+ .arg(&tmp_path)
+ .arg("--paging=never")
+ .arg("--color=never")
+ .arg("--style=numbers")
+ .arg("--decorations=always")
+ .arg("--wrap=character")
+ .assert()
+ .success()
+ .stdout(" 1 0123456789abcde\n f0123456789abcd\n ef0123456789abc\n def\n")
+ .stderr("");
+}
+
+#[test]
+fn terminal_width_arg_overrides_env() {
+ let tmp_dir = tempdir().expect("can create temporary directory");
+ let tmp_path = tmp_dir.path().join("long.txt");
+ std::fs::write(
+ &tmp_path,
+ "0123456789abcdef0123456789abcdef0123456789abcdef\n",
+ )
+ .expect("can write temporary file");
+
+ bat()
+ .env("BAT_WIDTH", "20")
+ .arg(&tmp_path)
+ .arg("--paging=never")
+ .arg("--color=never")
+ .arg("--style=numbers")
+ .arg("--decorations=always")
+ .arg("--wrap=character")
+ .arg("--terminal-width=10")
+ .assert()
+ .success()
+ .stdout(
+ " 1 01234\n 56789\n abcde\n f0123\n 45678\n 9abcd\n ef012\n 34567\n 89abc\n def\n",
+ )
+ .stderr("");
+}
+
#[test]
fn fail_non_existing() {
bat().arg("non-existing-file").assert().failure();
@@ -1474,6 +1525,7 @@ fn diagnostic_sanity_check() {
.assert()
.success()
.stdout(predicate::str::contains("BAT_PAGER="))
+ .stdout(predicate::str::contains("BAT_WIDTH="))
.stderr("");
}
From 01174b31f5276d5142fdcb4f74a8815ff6c4ea4f Mon Sep 17 00:00:00 2001
From: Asish Kumar
Date: Fri, 10 Apr 2026 14:36:49 +0000
Subject: [PATCH 042/130] Update changelog entry for #3687
Signed-off-by: Asish Kumar
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0539669b..ee313425 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,7 @@
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
-- Support configuring `--terminal-width` via `BAT_WIDTH`, see #0000 (@officialasishkumar)
+- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
- Added an initial `flake.nix` for a ready made development environment; see #3578 (@vorburger)
- Add `--quiet-empty` (`-E`) flag to suppress output when input is empty. Closes #1936, see #3563 (@NORMAL-EX)
From db647c98134e4a2aaeded2deee6d32f1dc7a9c27 Mon Sep 17 00:00:00 2001
From: orbisai0security
Date: Sun, 12 Apr 2026 11:43:12 +0000
Subject: [PATCH 043/130] fix: V-001 security vulnerability
Automated security fix generated by Orbis Security AI
---
tests/snapshots/generate_snapshots.py | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/tests/snapshots/generate_snapshots.py b/tests/snapshots/generate_snapshots.py
index bb7d69ba..f8c14541 100755
--- a/tests/snapshots/generate_snapshots.py
+++ b/tests/snapshots/generate_snapshots.py
@@ -23,18 +23,19 @@ def generate_style_snapshot(style):
def generate_snapshot(name, arguments):
- command = "cargo run -- --paging=never --color=never --decorations=always "
- command += "{args} sample.rs > output/{name}.snapshot.txt".format(
- name=name,
- args=arguments
- )
+ output_file = "output/{name}.snapshot.txt".format(name=name)
+ command = [
+ "cargo", "run", "--", "--paging=never", "--color=never",
+ "--decorations=always", arguments, "sample.rs"
+ ]
print("generating snapshot for {}".format(name))
- subprocess.call(command, shell=True)
+ with open(output_file, "w") as f:
+ subprocess.call(command, stdout=f)
def build_bat():
print("building bat")
- subprocess.call("cargo build", cwd="../..", shell=True)
+ subprocess.call(["cargo", "build"], cwd="../..")
def prepare_output_dir():
@@ -49,7 +50,7 @@ def modify_sample_file():
def undo_sample_file_modification():
print("undoing sample.rs modifications")
- subprocess.call("git checkout -- sample.rs", shell=True)
+ subprocess.call(["git", "checkout", "--", "sample.rs"])
build_bat()
From 6876a782daf2055b3ab743885b3868e3c268b66e Mon Sep 17 00:00:00 2001
From: Kira Security Bot
Date: Sun, 12 Apr 2026 12:37:43 +0000
Subject: [PATCH 044/130] Apply code changes: @orbisai0security can you address
code review comm...
---
tests/snapshots/generate_snapshots.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
mode change 100755 => 100644 tests/snapshots/generate_snapshots.py
diff --git a/tests/snapshots/generate_snapshots.py b/tests/snapshots/generate_snapshots.py
old mode 100755
new mode 100644
index f8c14541..db7839e9
--- a/tests/snapshots/generate_snapshots.py
+++ b/tests/snapshots/generate_snapshots.py
@@ -4,6 +4,7 @@ import itertools
import subprocess
import pathlib
import shutil
+from typing import Iterable
def generate_snapshots():
@@ -19,14 +20,14 @@ def generate_snapshots():
def generate_style_snapshot(style):
- generate_snapshot(style.replace(",", "_"), "--style={}".format(style))
+ generate_snapshot(style.replace(",", "_"), ["--style={}".format(style)])
-def generate_snapshot(name, arguments):
+def generate_snapshot(name: str, arguments: Iterable[str]):
output_file = "output/{name}.snapshot.txt".format(name=name)
command = [
"cargo", "run", "--", "--paging=never", "--color=never",
- "--decorations=always", arguments, "sample.rs"
+ "--decorations=always", *arguments, "sample.rs"
]
print("generating snapshot for {}".format(name))
with open(output_file, "w") as f:
From e89c515e9af81cb1058f3074cf01c08b952e69ed Mon Sep 17 00:00:00 2001
From: Jan Larres
Date: Mon, 13 Apr 2026 17:52:03 +1200
Subject: [PATCH 045/130] [Python] Support uv as script runner in shebang
---
CHANGELOG.md | 1 +
assets/patches/Python.sublime-syntax.patch | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee313425..4fc4e54e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@
- Improve native man pages and command help syntax highlighting by stripping overstriking, see #3517 (@akirk)
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
+- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
diff --git a/assets/patches/Python.sublime-syntax.patch b/assets/patches/Python.sublime-syntax.patch
index 36a50894..bacb9db9 100644
--- a/assets/patches/Python.sublime-syntax.patch
+++ b/assets/patches/Python.sublime-syntax.patch
@@ -2,6 +2,15 @@ diff --git syntaxes/01_Packages/Python/Python.sublime-syntax syntaxes/01_Package
index 2acd86d8..86257f7b 100644
--- syntaxes/01_Packages/Python/Python.sublime-syntax
+++ syntaxes/01_Packages/Python/Python.sublime-syntax
+@@ -25,7 +31,7 @@ file_extensions:
+ - wscript
+ - bazel
+ - bzl
+-first_line_match: ^#!\s*/.*\bpython(\d(\.\d)?)?\b
++first_line_match: ^#!\s*/.*\b(python(\d(\.\d)?)?|uv)\b
+ scope: source.python
+
+ variables:
@@ -988,10 +988,6 @@ contexts:
- match: \}
scope: punctuation.section.mapping-or-set.end.python
From 52763e0205f00b9b4f388e9f994aebe0fa0b41b5 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 20 Mar 2026 21:04:26 -0700
Subject: [PATCH 046/130] feat: add tclsh, wish, and expect shebang detection
for Tcl syntax
Add first_line_match to the Tcl syntax definition via a patch file,
enabling automatic Tcl highlighting for scripts with tclsh, wish,
or expect shebangs.
Fixes #3513
---
CHANGELOG.md | 1 +
assets/patches/Tcl.sublime-syntax.patch | 12 ++++++++++++
2 files changed, 13 insertions(+)
create mode 100644 assets/patches/Tcl.sublime-syntax.patch
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fc4e54e..3682bb82 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
## Features
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
+- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3513 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
diff --git a/assets/patches/Tcl.sublime-syntax.patch b/assets/patches/Tcl.sublime-syntax.patch
new file mode 100644
index 00000000..487a1841
--- /dev/null
+++ b/assets/patches/Tcl.sublime-syntax.patch
@@ -0,0 +1,12 @@
+diff --git syntaxes/01_Packages/TCL/Tcl.sublime-syntax syntaxes/01_Packages/TCL/Tcl.sublime-syntax
+index 1234567..abcdefg 100644
+--- syntaxes/01_Packages/TCL/Tcl.sublime-syntax
++++ syntaxes/01_Packages/TCL/Tcl.sublime-syntax
+@@ -3,6 +3,7 @@
+ # http://www.sublimetext.com/docs/3/syntax.html
+ name: Tcl
+ file_extensions:
+ - tcl
++first_line_match: ^\#!.*\b(tclsh|wish|expect)\b
+ scope: source.tcl
+ variables:
From 1f89178fce7dde0f6241091fff241768317a9c46 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 20 Mar 2026 21:06:42 -0700
Subject: [PATCH 047/130] fix: reference PR number in changelog entry
---
CHANGELOG.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3682bb82..79b65708 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,8 +8,12 @@
## Features
+<<<<<<< HEAD
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3513 (@mvanhorn)
+=======
+- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3647 (@mvanhorn)
+>>>>>>> 785fff9 (fix: reference PR number in changelog entry)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
From e070d105b57f854d982212265e2e52d6ea6d24ec Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Thu, 26 Mar 2026 22:22:54 -0700
Subject: [PATCH 048/130] test: add shebang regression tests and move changelog
to Syntaxes section
Add extensionless regression test files for tclsh, wish, and expect
shebangs so syntect bumps don't silently break first-line detection.
Move changelog entry from Features to Syntaxes per reviewer suggestion.
Co-Authored-By: Claude Opus 4.6
---
CHANGELOG.md | 6 ++--
.../regression_tests/issue_3647_expect | 2 ++
.../regression_tests/issue_3647_tclsh | 2 ++
.../examples/regression_tests/issue_3647_wish | 2 ++
tests/integration_tests.rs | 31 +++++++++++++++++++
5 files changed, 40 insertions(+), 3 deletions(-)
create mode 100644 tests/examples/regression_tests/issue_3647_expect
create mode 100644 tests/examples/regression_tests/issue_3647_tclsh
create mode 100644 tests/examples/regression_tests/issue_3647_wish
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79b65708..99222693 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,10 +10,9 @@
<<<<<<< HEAD
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
-- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3513 (@mvanhorn)
-=======
- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3647 (@mvanhorn)
->>>>>>> 785fff9 (fix: reference PR number in changelog entry)
+=======
+>>>>>>> 29c913f (test: add shebang regression tests and move changelog to Syntaxes section)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
@@ -50,6 +49,7 @@
## Syntaxes
+- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3647 (@mvanhorn)
- Change the URL of Zig submodule from GitHub to Codeberg, see #3519 (@sorairolake)
- Don't color strings inside CSV files, to make it easier to tell which column they belong to, see #3521 (@keith-hall)
- Add syntax highlighting support for COBOL, see #3584 (@adukhan99)
diff --git a/tests/examples/regression_tests/issue_3647_expect b/tests/examples/regression_tests/issue_3647_expect
new file mode 100644
index 00000000..3786df69
--- /dev/null
+++ b/tests/examples/regression_tests/issue_3647_expect
@@ -0,0 +1,2 @@
+#!/usr/bin/expect -f
+set timeout 30
diff --git a/tests/examples/regression_tests/issue_3647_tclsh b/tests/examples/regression_tests/issue_3647_tclsh
new file mode 100644
index 00000000..0f4b3c5c
--- /dev/null
+++ b/tests/examples/regression_tests/issue_3647_tclsh
@@ -0,0 +1,2 @@
+#!/usr/bin/env tclsh
+puts "Hello from tclsh"
diff --git a/tests/examples/regression_tests/issue_3647_wish b/tests/examples/regression_tests/issue_3647_wish
new file mode 100644
index 00000000..8bfe4baf
--- /dev/null
+++ b/tests/examples/regression_tests/issue_3647_wish
@@ -0,0 +1,2 @@
+#!/usr/bin/wish
+button .b -text "Click"
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 8ae4cfdd..9f18f598 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -4201,4 +4201,35 @@ fn plain_without_diff_still_works() {
.assert()
.success()
.stdout("line 1\nline 2 modified\nline 3\nline 4 added\n");
+#[test]
+fn tcl_shebang_detection_tclsh() {
+ bat()
+ .arg("--color=always")
+ .arg("--style=plain")
+ .arg("--decorations=always")
+ .arg("regression_tests/issue_3647_tclsh")
+ .assert()
+ .success();
+}
+
+#[test]
+fn tcl_shebang_detection_wish() {
+ bat()
+ .arg("--color=always")
+ .arg("--style=plain")
+ .arg("--decorations=always")
+ .arg("regression_tests/issue_3647_wish")
+ .assert()
+ .success();
+}
+
+#[test]
+fn tcl_shebang_detection_expect() {
+ bat()
+ .arg("--color=always")
+ .arg("--style=plain")
+ .arg("--decorations=always")
+ .arg("regression_tests/issue_3647_expect")
+ .assert()
+ .success();
}
From cafad6b03668abc1ab41e1e10e72fd449ac609e4 Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri, 27 Mar 2026 12:18:30 -0700
Subject: [PATCH 049/130] test: add highlighted outputs for Tcl shebang
regression tests
Generate highlighted test outputs for tclsh, wish, and expect
shebang detection files to prevent regressions.
---
CHANGELOG.md | 4 ----
tests/integration_tests.rs | 2 ++
tests/syntax-tests/highlighted/Tcl/expect_shebang | 7 +++++++
tests/syntax-tests/highlighted/Tcl/tclsh_shebang | 7 +++++++
tests/syntax-tests/highlighted/Tcl/wish_shebang | 5 +++++
5 files changed, 21 insertions(+), 4 deletions(-)
create mode 100644 tests/syntax-tests/highlighted/Tcl/expect_shebang
create mode 100644 tests/syntax-tests/highlighted/Tcl/tclsh_shebang
create mode 100644 tests/syntax-tests/highlighted/Tcl/wish_shebang
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99222693..8049cba3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,11 +8,7 @@
## Features
-<<<<<<< HEAD
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
-- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3647 (@mvanhorn)
-=======
->>>>>>> 29c913f (test: add shebang regression tests and move changelog to Syntaxes section)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index 9f18f598..432faed8 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -4201,6 +4201,8 @@ fn plain_without_diff_still_works() {
.assert()
.success()
.stdout("line 1\nline 2 modified\nline 3\nline 4 added\n");
+}
+
#[test]
fn tcl_shebang_detection_tclsh() {
bat()
diff --git a/tests/syntax-tests/highlighted/Tcl/expect_shebang b/tests/syntax-tests/highlighted/Tcl/expect_shebang
new file mode 100644
index 00000000..22f2101e
--- /dev/null
+++ b/tests/syntax-tests/highlighted/Tcl/expect_shebang
@@ -0,0 +1,7 @@
+[38;2;248;248;242m#!/usr/bin/expect -f[0m
+[38;2;248;248;242m# Expect script detected via expect shebang[0m
+[38;2;248;248;242mset timeout 30[0m
+[38;2;248;248;242mspawn ssh user@host[0m
+[38;2;248;248;242mexpect "password:"[0m
+[38;2;248;248;242msend "secret\r"[0m
+[38;2;248;248;242mexpect eof[0m
diff --git a/tests/syntax-tests/highlighted/Tcl/tclsh_shebang b/tests/syntax-tests/highlighted/Tcl/tclsh_shebang
new file mode 100644
index 00000000..de50978f
--- /dev/null
+++ b/tests/syntax-tests/highlighted/Tcl/tclsh_shebang
@@ -0,0 +1,7 @@
+[38;2;248;248;242m#!/usr/bin/env tclsh[0m
+[38;2;248;248;242m# Tcl script detected via tclsh shebang[0m
+[38;2;248;248;242mputs "Hello from tclsh"[0m
+[38;2;248;248;242mset x 42[0m
+[38;2;248;248;242mif {$x > 0} {[0m
+[38;2;248;248;242m puts "positive"[0m
+[38;2;248;248;242m}[0m
diff --git a/tests/syntax-tests/highlighted/Tcl/wish_shebang b/tests/syntax-tests/highlighted/Tcl/wish_shebang
new file mode 100644
index 00000000..134b185d
--- /dev/null
+++ b/tests/syntax-tests/highlighted/Tcl/wish_shebang
@@ -0,0 +1,5 @@
+[38;2;248;248;242m#!/usr/bin/wish[0m
+[38;2;248;248;242m# Tk script detected via wish shebang[0m
+[38;2;248;248;242mpackage require Tk[0m
+[38;2;248;248;242mbutton .b -text "Click" -command {puts "clicked"}[0m
+[38;2;248;248;242mpack .b[0m
From 77ea750e662d6dd21196acec1cae32517fe57dac Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Sat, 18 Apr 2026 06:58:45 -0700
Subject: [PATCH 050/130] test(Tcl): add shebang source files for regression
test
CI reported FileNotFoundError for Tcl/{tclsh,expect,wish}_shebang in
tests/syntax-tests/source/Tcl/. The highlighted files existed but the
source files had been omitted, so create_highlighted_versions.py had
nothing to read from.
Source files match the shebang regression test inputs at
tests/examples/regression_tests/issue_3647_*.
---
tests/syntax-tests/source/Tcl/expect_shebang | 7 +++++++
tests/syntax-tests/source/Tcl/tclsh_shebang | 7 +++++++
tests/syntax-tests/source/Tcl/wish_shebang | 5 +++++
3 files changed, 19 insertions(+)
create mode 100644 tests/syntax-tests/source/Tcl/expect_shebang
create mode 100644 tests/syntax-tests/source/Tcl/tclsh_shebang
create mode 100644 tests/syntax-tests/source/Tcl/wish_shebang
diff --git a/tests/syntax-tests/source/Tcl/expect_shebang b/tests/syntax-tests/source/Tcl/expect_shebang
new file mode 100644
index 00000000..ef288ba8
--- /dev/null
+++ b/tests/syntax-tests/source/Tcl/expect_shebang
@@ -0,0 +1,7 @@
+#!/usr/bin/expect -f
+# Expect script detected via expect shebang
+set timeout 30
+spawn ssh user@host
+expect "password:"
+send "secret\r"
+expect eof
diff --git a/tests/syntax-tests/source/Tcl/tclsh_shebang b/tests/syntax-tests/source/Tcl/tclsh_shebang
new file mode 100644
index 00000000..08faa4d6
--- /dev/null
+++ b/tests/syntax-tests/source/Tcl/tclsh_shebang
@@ -0,0 +1,7 @@
+#!/usr/bin/env tclsh
+# Tcl script detected via tclsh shebang
+puts "Hello from tclsh"
+set x 42
+if {$x > 0} {
+ puts "positive"
+}
diff --git a/tests/syntax-tests/source/Tcl/wish_shebang b/tests/syntax-tests/source/Tcl/wish_shebang
new file mode 100644
index 00000000..690e73ef
--- /dev/null
+++ b/tests/syntax-tests/source/Tcl/wish_shebang
@@ -0,0 +1,5 @@
+#!/usr/bin/wish
+# Tk script detected via wish shebang
+package require Tk
+button .b -text "Click" -command {puts "clicked"}
+pack .b
From 0ecdeb28d43c7a259c59cc35dd5e1f0c2a266d3e Mon Sep 17 00:00:00 2001
From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Sat, 18 Apr 2026 09:42:26 -0700
Subject: [PATCH 051/130] test(Tcl): regenerate shebang golden files to match
patched syntax
The Tcl.sublime-syntax.patch adds first_line_match so bat recognizes
tclsh/wish/expect shebang files as Tcl. With the patch applied, the
leading '#!/...' and '# comment' lines get tokenized as Tcl comments
and rendered in Monokai-Extended's comment color instead of default
foreground.
The goldens were generated against unpatched bat in the original
commit, so CI's 'Run tests with updated syntaxes and themes' job (which
runs assets/create.sh before the regression test) disagreed. Regenerate
against the patched build so the regression test passes.
---
tests/syntax-tests/highlighted/Tcl/expect_shebang | 14 +++++++-------
tests/syntax-tests/highlighted/Tcl/tclsh_shebang | 12 ++++++------
tests/syntax-tests/highlighted/Tcl/wish_shebang | 10 +++++-----
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/tests/syntax-tests/highlighted/Tcl/expect_shebang b/tests/syntax-tests/highlighted/Tcl/expect_shebang
index 22f2101e..871eed6c 100644
--- a/tests/syntax-tests/highlighted/Tcl/expect_shebang
+++ b/tests/syntax-tests/highlighted/Tcl/expect_shebang
@@ -1,7 +1,7 @@
-[38;2;248;248;242m#!/usr/bin/expect -f[0m
-[38;2;248;248;242m# Expect script detected via expect shebang[0m
-[38;2;248;248;242mset timeout 30[0m
-[38;2;248;248;242mspawn ssh user@host[0m
-[38;2;248;248;242mexpect "password:"[0m
-[38;2;248;248;242msend "secret\r"[0m
-[38;2;248;248;242mexpect eof[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m!/usr/bin/expect -f[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m Expect script detected via expect shebang[0m
+[38;2;249;38;114mset[0m[38;2;248;248;242m timeout [0m[38;2;190;132;255m30[0m
+[38;2;248;248;242mspawn[0m[38;2;248;248;242m ssh user@host[0m
+[38;2;248;248;242mexpect[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mpassword:[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242msend[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msecret[0m[38;2;190;132;255m\r[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242mexpect[0m[38;2;248;248;242m [0m[38;2;249;38;114meof[0m
diff --git a/tests/syntax-tests/highlighted/Tcl/tclsh_shebang b/tests/syntax-tests/highlighted/Tcl/tclsh_shebang
index de50978f..5461f5f8 100644
--- a/tests/syntax-tests/highlighted/Tcl/tclsh_shebang
+++ b/tests/syntax-tests/highlighted/Tcl/tclsh_shebang
@@ -1,7 +1,7 @@
-[38;2;248;248;242m#!/usr/bin/env tclsh[0m
-[38;2;248;248;242m# Tcl script detected via tclsh shebang[0m
-[38;2;248;248;242mputs "Hello from tclsh"[0m
-[38;2;248;248;242mset x 42[0m
-[38;2;248;248;242mif {$x > 0} {[0m
-[38;2;248;248;242m puts "positive"[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m!/usr/bin/env tclsh[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m Tcl script detected via tclsh shebang[0m
+[38;2;249;38;114mputs[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mHello from tclsh[0m[38;2;230;219;116m"[0m
+[38;2;249;38;114mset[0m[38;2;248;248;242m x [0m[38;2;190;132;255m42[0m
+[38;2;249;38;114mif[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;255;255;255m$[0m[38;2;255;255;255mx[0m[38;2;248;248;242m [0m[38;2;249;38;114m>[0m[38;2;248;248;242m [0m[38;2;190;132;255m0[0m[38;2;248;248;242m}[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mputs[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mpositive[0m[38;2;230;219;116m"[0m
[38;2;248;248;242m}[0m
diff --git a/tests/syntax-tests/highlighted/Tcl/wish_shebang b/tests/syntax-tests/highlighted/Tcl/wish_shebang
index 134b185d..94fb971f 100644
--- a/tests/syntax-tests/highlighted/Tcl/wish_shebang
+++ b/tests/syntax-tests/highlighted/Tcl/wish_shebang
@@ -1,5 +1,5 @@
-[38;2;248;248;242m#!/usr/bin/wish[0m
-[38;2;248;248;242m# Tk script detected via wish shebang[0m
-[38;2;248;248;242mpackage require Tk[0m
-[38;2;248;248;242mbutton .b -text "Click" -command {puts "clicked"}[0m
-[38;2;248;248;242mpack .b[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m!/usr/bin/wish[0m
+[38;2;117;113;94m#[0m[38;2;117;113;94m Tk script detected via wish shebang[0m
+[38;2;249;38;114mpackage[0m[38;2;248;248;242m require Tk[0m
+[38;2;248;248;242mbutton[0m[38;2;248;248;242m .b [0m[38;2;249;38;114m-[0m[38;2;248;248;242mtext [0m[38;2;230;219;116m"[0m[38;2;230;219;116mClick[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;248;248;242mcommand [0m[38;2;248;248;242m{[0m[38;2;249;38;114mputs[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mclicked[0m[38;2;230;219;116m"[0m[38;2;248;248;242m}[0m
+[38;2;248;248;242mpack[0m[38;2;248;248;242m .b[0m
From ea7fafca1e6191dbfc4c55f039930e350b4412b2 Mon Sep 17 00:00:00 2001
From: Barry <100205797+barry3406@users.noreply.github.com>
Date: Sun, 19 Apr 2026 14:19:00 -0700
Subject: [PATCH 052/130] windows: statically link the CRT to remove vcruntime
dependency
Add a .cargo/config.toml that sets target-feature=+crt-static for the
i686, x86_64, and aarch64 MSVC targets so bat.exe no longer depends on
the vcruntime DLL. Mirrors the fix applied to fd in sharkdp/fd#1891.
Closes #3634
---
.cargo/config.toml | 11 +++++++++++
CHANGELOG.md | 1 +
2 files changed, 12 insertions(+)
create mode 100644 .cargo/config.toml
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000..67a37e28
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,11 @@
+# On Windows MSVC, statically link the C runtime so that the resulting EXE does
+# not depend on the vcruntime DLL.
+#
+# See: https://github.com/sharkdp/bat/issues/3634
+
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
+[target.i686-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
+[target.aarch64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8049cba3..5a37d298 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@
- Bump MSRV to 1.88, update `time` crate to 0.3.47 to fix RUSTSEC-2026-0009, see #3581 (@NORMAL-EX)
- Allow home and end keys to be used with builtin pager, see #3651 (@keith-hall)
- Builtin syntax mapping: cleanup matcher glob parsing logic #3652 (@cyqsimon)
+- Statically link the CRT for MSVC builds via Cargo config to avoid runtime DLL dependencies. Closes #3634, see #3692 (@barry3406)
## Syntaxes
From 71c894e843522fd1804283b7bd347cb2c31fad21 Mon Sep 17 00:00:00 2001
From: curious-rabbit
Date: Mon, 20 Apr 2026 14:25:49 +0200
Subject: [PATCH 053/130] santize filenames
---
CHANGELOG.md | 1 +
src/assets.rs | 16 +++++++----
src/bin/bat/main.rs | 4 ++-
src/input.rs | 16 +++++------
src/lib.rs | 1 +
src/preprocessor.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++
src/printer.rs | 12 ++++++---
7 files changed, 98 insertions(+), 18 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fc4e54e..18a322a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
+- Sanitize control characters in filenames before displaying them in the file header, error messages, and the terminal title, preventing ANSI escape injection via crafted filenames. Closes #3054, see #3691 (@curious-rabbit)
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
diff --git a/src/assets.rs b/src/assets.rs
index 29247bd7..9483f032 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -158,7 +158,9 @@ impl HighlightingAssets {
let syntax_match = mapping.get_syntax_for(path);
if let Some(MappingTarget::MapToUnknown) = syntax_match {
- return Err(Error::UndetectedSyntax(path.to_string_lossy().into()));
+ return Err(Error::UndetectedSyntax(
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
+ ));
}
if let Some(MappingTarget::MapTo(syntax_name)) = syntax_match {
@@ -175,13 +177,17 @@ impl HighlightingAssets {
) {
(Some(syntax), _) => Ok(syntax),
- (_, Some(MappingTarget::MapExtensionToUnknown)) => {
- Err(Error::UndetectedSyntax(path.to_string_lossy().into()))
- }
+ (_, Some(MappingTarget::MapExtensionToUnknown)) => Err(Error::UndetectedSyntax(
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
+ )),
_ => self
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
- .ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
+ .ok_or_else(|| {
+ Error::UndetectedSyntax(crate::preprocessor::sanitize_for_terminal(
+ &path.to_string_lossy(),
+ ))
+ }),
}
}
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index f3fa01f4..71ed8527 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -259,7 +259,9 @@ pub fn list_themes(
fn set_terminal_title_to(new_terminal_title: String) {
let osc_command_for_setting_terminal_title = "\x1b]0;";
let osc_end_command = "\x07";
- print!("{osc_command_for_setting_terminal_title}{new_terminal_title}{osc_end_command}");
+ // Prevent BEL/ESC/C1 bytes in the title from terminating or nesting the OSC.
+ let safe_title = bat::sanitize_for_terminal(&new_terminal_title);
+ print!("{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}");
io::stdout().flush().unwrap();
}
diff --git a/src/input.rs b/src/input.rs
index 30f13f98..231e2a34 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -216,20 +216,20 @@ impl<'a> Input<'a> {
description,
metadata: self.metadata,
reader: {
- let mut file = File::open(&path)
- .map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
+ let path_display =
+ crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy());
+ let mut file =
+ File::open(&path).map_err(|e| format!("'{path_display}': {e}"))?;
if file.metadata()?.is_dir() {
- return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
+ return Err(format!("'{path_display}' is a directory.").into());
}
if let Some(stdout) = stdout_identifier {
- let input_identifier = Identifier::try_from(file).map_err(|e| {
- format!("{}: Error identifying file: {e}", path.to_string_lossy())
- })?;
+ let input_identifier = Identifier::try_from(file)
+ .map_err(|e| format!("{path_display}: Error identifying file: {e}"))?;
if stdout.surely_conflicts_with(&input_identifier) {
return Err(format!(
- "IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
- path.to_string_lossy()
+ "IO circle detected. The input from '{path_display}' is also an output. Aborting to avoid infinite loop.",
)
.into());
}
diff --git a/src/lib.rs b/src/lib.rs
index 4c60f10e..685214dc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -54,6 +54,7 @@ mod vscreen;
pub(crate) mod wrapping;
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
+pub use preprocessor::sanitize_for_terminal;
pub use preprocessor::StripAnsiMode;
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
diff --git a/src/preprocessor.rs b/src/preprocessor.rs
index 6b4e2935..a34f9f9e 100644
--- a/src/preprocessor.rs
+++ b/src/preprocessor.rs
@@ -149,6 +149,35 @@ pub fn strip_ansi(line: &str) -> String {
buffer
}
+/// Escape C0, DEL, and C1 control characters so a string from an untrusted
+/// filename or path can be safely written to the terminal.
+pub fn sanitize_for_terminal(input: &str) -> String {
+ if !input
+ .chars()
+ .any(|c| matches!(c, '\x00'..='\x08' | '\x0A'..='\x1F' | '\x7F'..='\u{9F}'))
+ {
+ return input.to_owned();
+ }
+
+ let mut out = String::with_capacity(input.len() + 8);
+ for c in input.chars() {
+ match c {
+ '\t' => out.push('\t'),
+ '\x00'..='\x1F' => {
+ out.push('^');
+ out.push(char::from_u32(0x40 + c as u32).unwrap_or('?'));
+ }
+ '\x7F' => out.push_str("^?"),
+ '\u{80}'..='\u{9F}' => {
+ use std::fmt::Write as _;
+ let _ = write!(out, "\\u{{{:x}}}", c as u32);
+ }
+ other => out.push(other),
+ }
+ }
+ out
+}
+
/// Strips overstrike sequences (backspace formatting) from input.
///
/// Overstrike formatting is used by man pages and some help output:
@@ -261,3 +290,40 @@ fn test_strip_overstrike() {
// Unicode with overstrike
assert_eq!(strip_overstrike("ä\x08äöü", 2), "äöü");
}
+
+#[test]
+fn test_sanitize_for_terminal_passthrough() {
+ assert_eq!(sanitize_for_terminal(""), "");
+ assert_eq!(sanitize_for_terminal("hello.txt"), "hello.txt");
+ assert_eq!(sanitize_for_terminal("résumé.pdf"), "résumé.pdf");
+ assert_eq!(sanitize_for_terminal("日本語.md"), "日本語.md");
+ assert_eq!(
+ sanitize_for_terminal("path/with spaces/file.log"),
+ "path/with spaces/file.log"
+ );
+ assert_eq!(sanitize_for_terminal("a\tb"), "a\tb");
+}
+
+#[test]
+fn test_sanitize_for_terminal_c0_controls() {
+ assert_eq!(
+ sanitize_for_terminal("\x1b[31mINJECTED\x1b[0m.txt"),
+ "^[[31mINJECTED^[[0m.txt"
+ );
+ assert_eq!(sanitize_for_terminal("bad\x07rest"), "bad^Grest");
+ assert_eq!(sanitize_for_terminal("\x00\x08\n\r\x7F"), "^@^H^J^M^?");
+ assert_eq!(sanitize_for_terminal("\u{9b}31m"), "\\u{9b}31m");
+ assert_eq!(
+ sanitize_for_terminal("\u{9d}0;pwned\x07"),
+ "\\u{9d}0;pwned^G"
+ );
+}
+
+#[test]
+fn test_sanitize_for_terminal_idempotent_on_sanitized() {
+ let dirty = "\x1b]0;pwned\x07file.txt";
+ let clean = sanitize_for_terminal(dirty);
+ assert_eq!(sanitize_for_terminal(&clean), clean);
+ assert!(!clean.contains('\x1b'));
+ assert!(!clean.contains('\x07'));
+}
diff --git a/src/printer.rs b/src/printer.rs
index c58c914d..17f7aa30 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -29,7 +29,9 @@ use crate::error::*;
use crate::input::OpenedInput;
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
use crate::output::OutputHandle;
-use crate::preprocessor::{expand_tabs, replace_nonprintable, strip_ansi, strip_overstrike};
+use crate::preprocessor::{
+ expand_tabs, replace_nonprintable, sanitize_for_terminal, strip_ansi, strip_overstrike,
+};
use crate::style::StyleComponent;
use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
@@ -489,7 +491,7 @@ impl Printer for InteractivePrinter<'_> {
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
to show the binary file contents.",
Yellow.paint("[bat warning]"),
- input.description.summary(),
+ sanitize_for_terminal(&input.description.summary()),
)?;
} else if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;
@@ -543,9 +545,11 @@ impl Printer for InteractivePrinter<'_> {
"{}{}{mode}",
description
.kind()
- .map(|kind| format!("{kind}: "))
+ .map(|kind| format!("{}: ", sanitize_for_terminal(kind)))
.unwrap_or_else(|| "".into()),
- self.colors.header_value.paint(description.title()),
+ self.colors
+ .header_value
+ .paint(sanitize_for_terminal(description.title())),
);
self.print_header_multiline_component(handle, &header_filename)
}
From c3df0e6a883f128974e4ccb7d71555cf4166045b Mon Sep 17 00:00:00 2001
From: Ish West
Date: Sat, 25 Apr 2026 17:37:36 +0200
Subject: [PATCH 054/130] Fixed a bug with hardcoded terminal probing when
`--list-themes` is called
---
src/bin/bat/app.rs | 2 +-
src/bin/bat/main.rs | 9 ++++-----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs
index f95bbb91..3124cdcd 100644
--- a/src/bin/bat/app.rs
+++ b/src/bin/bat/app.rs
@@ -645,7 +645,7 @@ impl App {
Ok(styled_components)
}
- fn theme_options(&self) -> ThemeOptions {
+ pub(crate) fn theme_options(&self) -> ThemeOptions {
Self::theme_options_from_matches(&self.matches)
}
diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs
index f3fa01f4..5184cf99 100644
--- a/src/bin/bat/main.rs
+++ b/src/bin/bat/main.rs
@@ -17,7 +17,6 @@ use std::path::Path;
use std::process;
use bat::output::{OutputHandle, OutputType};
-use bat::theme::DetectColorScheme;
use nu_ansi_term::Color::Green;
use nu_ansi_term::Style;
@@ -39,7 +38,7 @@ use bat::{
error::*,
input::Input,
style::{StyleComponent, StyleComponents},
- theme::{color_scheme, default_theme, ColorScheme},
+ theme::{default_theme, theme, ColorScheme, ThemeOptions},
MappingTarget, PagingMode,
};
@@ -197,7 +196,7 @@ pub fn list_themes(
cfg: &Config,
config_dir: &Path,
cache_dir: &Path,
- detect_color_scheme: DetectColorScheme,
+ theme_options: ThemeOptions,
) -> Result<()> {
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
let mut config = cfg.clone();
@@ -206,7 +205,7 @@ pub fn list_themes(
config.language = Some("Rust");
config.style_components = StyleComponents(style);
- let default_theme_name = default_theme(color_scheme(detect_color_scheme).unwrap_or_default());
+ let default_theme_name = theme(theme_options).to_string();
let mut buf = String::new();
let mut handle = OutputHandle::FmtWrite(&mut buf);
@@ -426,7 +425,7 @@ fn run() -> Result {
};
run_controller(inputs, &plain_config, cache_dir)
} else if app.matches.get_flag("list-themes") {
- list_themes(&config, config_dir, cache_dir, DetectColorScheme::default())?;
+ list_themes(&config, config_dir, cache_dir, app.theme_options())?;
Ok(true)
} else if app.matches.get_flag("config-file") {
println!("{}", config_file().to_string_lossy());
From 044d445adc5b8136bee14ab21830e43836edffd5 Mon Sep 17 00:00:00 2001
From: Ish West
Date: Sat, 25 Apr 2026 21:17:02 +0200
Subject: [PATCH 055/130] Updated CHANGELOG.md
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a37d298..91171c92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
+- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value (regression introduced in bc42149a). (@optimistiCli)
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
From 4144059e7a8c7dcc0fcdd40431f6e05cb4ee40cf Mon Sep 17 00:00:00 2001
From: Ish West
Date: Sat, 25 Apr 2026 21:30:03 +0200
Subject: [PATCH 056/130] Fixed CHANGELOG.md entry format
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91171c92..bd76e2e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,7 +21,7 @@
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
-- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value (regression introduced in bc42149a). (@optimistiCli)
+- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value, see #3700 (regression introduced in bc42149a). (@optimistiCli)
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
From f39d63b85d5b4bc675c718ef7832d786ed38a9a1 Mon Sep 17 00:00:00 2001
From: YoshKoz <77861115+YoshKoz@users.noreply.github.com>
Date: Sun, 19 Apr 2026 23:19:53 +0200
Subject: [PATCH 057/130] fix(zsh): use newline-splitting for language
completions to fix word-splitting on names with spaces
Language names like "HTML (Jinja2)" and "Apache Conf" contain spaces, which
caused $() command substitution to word-split them into garbage tokens. Use
${(f)"$(...)"} (split on newlines only) to match the pattern already used
for theme completions on line 100.
Fixes #2897
---
assets/completions/bat.zsh.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/completions/bat.zsh.in b/assets/completions/bat.zsh.in
index 3c3f81d1..f8de56e4 100644
--- a/assets/completions/bat.zsh.in
+++ b/assets/completions/bat.zsh.in
@@ -90,7 +90,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
languages)
local IFS=$'\n'
local -a languages
- languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
+ languages=( ${(f)"$({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }')"} )
_describe 'language' languages && ret=0
;;
From 5722311b2e96f6c6751e848a99c265e386ab43f3 Mon Sep 17 00:00:00 2001
From: Yoshi Tacke <77861115+YoshKoz@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:06:00 +0200
Subject: [PATCH 058/130] docs(changelog): add bugfix entry for zsh completion
fix
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a37d298..8eabb7d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
- Fixed syntax tests path, see #3610 (@foxfromworld)
+- Fix zsh tab completion word-splitting language names containing spaces (e.g. `HTML (Jinja2)`, `Apache Conf`), see #3693 (@YoshKoz)
## Other
- Use git version of cross. See #3533 (@OctopusET)
From 64567c4819ff76cec66a74e87c9e47b1f6f9b609 Mon Sep 17 00:00:00 2001
From: lawrence3699
Date: Tue, 28 Apr 2026 01:11:06 +1000
Subject: [PATCH 059/130] Propagate initial input read errors
---
CHANGELOG.md | 1 +
src/input.rs | 44 +++++++++++++++++++++++++++++++++++---------
src/lessopen.rs | 4 ++--
3 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8eabb7d4..066b1161 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
+- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699)
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
diff --git a/src/input.rs b/src/input.rs
index 30f13f98..a3420123 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -207,7 +207,7 @@ impl<'a> Input<'a> {
kind: OpenedInputKind::StdIn,
description,
metadata: self.metadata,
- reader: InputReader::new(stdin),
+ reader: InputReader::try_new(stdin)?,
})
}
@@ -236,14 +236,14 @@ impl<'a> Input<'a> {
file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened...");
}
- InputReader::new(BufReader::new(file))
+ InputReader::try_new(BufReader::new(file))?
},
}),
InputKind::CustomReader(reader) => Ok(OpenedInput {
description,
kind: OpenedInputKind::CustomReader,
metadata: self.metadata,
- reader: InputReader::new(BufReader::new(reader)),
+ reader: InputReader::try_new(BufReader::new(reader))?,
}),
}
}
@@ -257,24 +257,29 @@ pub(crate) struct InputReader<'a> {
}
impl<'a> InputReader<'a> {
- pub(crate) fn new(mut reader: R) -> InputReader<'a> {
+ #[cfg(test)]
+ pub(crate) fn new(reader: R) -> InputReader<'a> {
+ Self::try_new(reader).expect("reading the first line failed")
+ }
+
+ pub(crate) fn try_new(mut reader: R) -> io::Result> {
let mut first_line = vec![];
- reader.read_until(b'\n', &mut first_line).ok();
+ reader.read_until(b'\n', &mut first_line)?;
let content_type = inspect_content_type(&first_line);
if content_type == Some(ContentType::UTF_16LE) {
- read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok();
+ read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A)?;
} else if content_type == Some(ContentType::UTF_16BE) {
- read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok();
+ read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00)?;
}
- InputReader {
+ Ok(InputReader {
inner: Box::new(reader),
first_line,
content_type,
unbuffered: false,
- }
+ })
}
pub(crate) fn read_line(&mut self, buf: &mut Vec) -> io::Result {
@@ -405,6 +410,27 @@ fn non_zip_pk_prefix_is_not_treated_as_binary() {
);
}
+#[test]
+fn input_open_returns_initial_read_errors() {
+ struct FailingRead;
+
+ impl Read for FailingRead {
+ fn read(&mut self, _buf: &mut [u8]) -> io::Result {
+ Err(io::Error::other("initial read failed"))
+ }
+ }
+
+ let input = Input::from_reader(Box::new(FailingRead));
+ let result = input.open(io::empty(), None);
+
+ assert!(result.is_err());
+ assert!(result
+ .err()
+ .unwrap()
+ .to_string()
+ .contains("initial read failed"));
+}
+
#[test]
fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
diff --git a/src/lessopen.rs b/src/lessopen.rs
index 966bc2c0..116206ba 100644
--- a/src/lessopen.rs
+++ b/src/lessopen.rs
@@ -155,7 +155,7 @@ impl LessOpenPreprocessor {
Ok(OpenedInput {
kind,
- reader: InputReader::new(BufReader::new(
+ reader: InputReader::try_new(BufReader::new(
if matches!(self.kind, LessOpenKind::TempFile) {
let lessopen_string = match String::from_utf8(lessopen_stdout) {
Ok(string) => string,
@@ -192,7 +192,7 @@ impl LessOpenPreprocessor {
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)),
}
},
- )),
+ ))?,
metadata: input.metadata,
description: input.description,
})
From bceb260e91919aebe48f4230f69d5c3f0ebee1e9 Mon Sep 17 00:00:00 2001
From: guille
Date: Fri, 24 Apr 2026 16:20:49 +0200
Subject: [PATCH 060/130] Improve Kotlin syntax
---
.gitmodules | 6 +-
CHANGELOG.md | 1 +
assets/syntaxes/02_Extra/Kotlin | 2 +-
.../syntaxes/02_Extra/Kotlin.sublime-syntax | 398 ------------------
tests/syntax-tests/highlighted/Kotlin/test.kt | 104 ++---
5 files changed, 57 insertions(+), 454 deletions(-)
delete mode 100644 assets/syntaxes/02_Extra/Kotlin.sublime-syntax
diff --git a/.gitmodules b/.gitmodules
index b64280c6..20e0bbb4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -49,9 +49,6 @@
[submodule "assets/themes/zenburn"]
path = assets/themes/zenburn
url = https://github.com/colinta/zenburn.git
-[submodule "assets/syntaxes/Kotlin"]
- path = assets/syntaxes/02_Extra/Kotlin
- url = https://github.com/vkostyukov/kotlin-sublime-package
[submodule "assets/syntaxes/Elm"]
path = assets/syntaxes/02_Extra/Elm
url = https://github.com/elm-community/SublimeElmLanguageSupport
@@ -281,3 +278,6 @@
[submodule "assets/syntaxes/02_Extra/COBOL"]
path = assets/syntaxes/02_Extra/COBOL
url = https://github.com/adukhan99/sublime_cobol.git
+[submodule "assets/syntaxes/02_Extra/Kotlin"]
+ path = assets/syntaxes/02_Extra/Kotlin
+ url = https://github.com/guille/sublime-kotlin
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 066b1161..341dbf42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@
- Fixed manpage syntax so that ANSI escape codes don't get incorrectly highlighted and thus broken, see #3586 (@BlueElectivire)
- Map several Google Cloud CLI config files to their appropriate syntax #3635 (@victor-gp)
- Map all ignore dotfiles to Git Ignore syntax #3636 (@victor-gp)
+- Improved Kotlin syntax, see #3698 (@guille)
## Themes
diff --git a/assets/syntaxes/02_Extra/Kotlin b/assets/syntaxes/02_Extra/Kotlin
index aeeed278..c3536941 160000
--- a/assets/syntaxes/02_Extra/Kotlin
+++ b/assets/syntaxes/02_Extra/Kotlin
@@ -1 +1 @@
-Subproject commit aeeed2780b04aea3d293c547c24cae27cafef0c5
+Subproject commit c353694169c047bd746c93c214289fc52d152cae
diff --git a/assets/syntaxes/02_Extra/Kotlin.sublime-syntax b/assets/syntaxes/02_Extra/Kotlin.sublime-syntax
deleted file mode 100644
index eab59192..00000000
--- a/assets/syntaxes/02_Extra/Kotlin.sublime-syntax
+++ /dev/null
@@ -1,398 +0,0 @@
-%YAML 1.2
----
-# http://www.sublimetext.com/docs/3/syntax.html
-name: Kotlin
-file_extensions:
- - kt
- - kts
-scope: source.Kotlin
-contexts:
- main:
- - include: comments
- - match: '^\s*(package)\b(?:\s*([^ ;$]+)\s*)?'
- captures:
- 1: keyword.other.kotlin
- 2: entity.name.package.kotlin
- - include: imports
- - include: statements
- classes:
- - match: (?"
- pop: true
- - include: generics
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: "(?={|$)"
- pop: true
- - match: \w+
- scope: entity.other.inherited-class.kotlin
- - match: \(
- push:
- - match: \)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- comments:
- - match: /\*
- captures:
- 0: punctuation.definition.comment.kotlin
- push:
- - meta_scope: comment.block.kotlin
- - match: \*/
- captures:
- 0: punctuation.definition.comment.kotlin
- pop: true
- - match: \s*((//).*$\n?)
- captures:
- 1: comment.line.double-slash.kotlin
- 2: punctuation.definition.comment.kotlin
- constants:
- - match: \b(true|false|null|this|super)\b
- scope: constant.language.kotlin
- - match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b'
- scope: constant.numeric.kotlin
- - match: '\b([A-Z][A-Z0-9_]+)\b'
- scope: constant.other.kotlin
- expressions:
- - match: \(
- push:
- - match: \)
- pop: true
- - include: expressions
- - include: types
- - include: strings
- - include: constants
- - include: comments
- - include: keywords
- functions:
- - match: (?=\s*\b(?:fun)\b)
- push:
- - match: '(?=$|\})'
- pop: true
- - match: \b(fun)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=\()
- pop: true
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - match: '([\.<\?>\w]+\.)?(\w+)'
- captures:
- 2: entity.name.function.kotlin
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: "(?={|=|$)"
- pop: true
- - include: types
- - match: '\{'
- push:
- - match: '(?=\})'
- pop: true
- - include: statements
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- generics:
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|>)
- pop: true
- - include: types
- - include: keywords
- - match: \w+
- scope: storage.type.generic.kotlin
- getters-and-setters:
- - match: \b(get)\b\s*\(\s*\)
- captures:
- 1: entity.name.function.kotlin
- push:
- - match: '\}|(?=\bset\b)|$'
- pop: true
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$|\bset\b)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: expressions
- - match: \b(set)\b\s*(?=\()
- captures:
- 1: entity.name.function.kotlin
- push:
- - match: '\}|(?=\bget\b)|$'
- pop: true
- - match: \(
- push:
- - match: \)
- pop: true
- - include: parameters
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$|\bset\b)
- pop: true
- - include: expressions
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: expressions
- imports:
- - match: '^\s*(import)\s+[^ $]+\s+(as)?'
- captures:
- 1: keyword.other.kotlin
- 2: keyword.other.kotlin
- keywords:
- - match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b
- scope: storage.modifier.kotlin
- - match: \b(try|catch|finally|throw)\b
- scope: keyword.control.catch-exception.kotlin
- - match: \b(if|else|while|for|do|return|when|where|break|continue)\b
- scope: keyword.control.kotlin
- - match: \b(in|is|!in|!is|as|as\?|assert)\b
- scope: keyword.operator.kotlin
- - match: (==|!=|===|!==|<=|>=|<|>)
- scope: keyword.operator.comparison.kotlin
- - match: (=)
- scope: keyword.operator.assignment.kotlin
- - match: (::)
- scope: keyword.operator.kotlin
- - match: (:)
- scope: keyword.operator.declaration.kotlin
- - match: \b(by)\b
- scope: keyword.other.by.kotlin
- - match: (\?\.)
- scope: keyword.operator.safenav.kotlin
- - match: (\.)
- scope: keyword.operator.dot.kotlin
- - match: (\?:)
- scope: keyword.operator.elvis.kotlin
- - match: (\-\-|\+\+)
- scope: keyword.operator.increment-decrement.kotlin
- - match: (\+=|\-=|\*=|\/=)
- scope: keyword.operator.arithmetic.assign.kotlin
- - match: (\.\.)
- scope: keyword.operator.range.kotlin
- - match: (\-|\+|\*|\/|%)
- scope: keyword.operator.arithmetic.kotlin
- - match: (!|&&|\|\|)
- scope: keyword.operator.logical.kotlin
- - match: (;)
- scope: punctuation.terminator.kotlin
- namespaces:
- - match: \b(namespace)\b
- scope: keyword.other.kotlin
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- parameters:
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|\)|=)
- pop: true
- - include: types
- - match: (=)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?=,|\))
- pop: true
- - include: expressions
- - include: keywords
- - match: \w+
- scope: variable.parameter.function.kotlin
- statements:
- - include: namespaces
- - include: typedefs
- - include: classes
- - include: functions
- - include: variables
- - include: getters-and-setters
- - include: expressions
- strings:
- - match: '"""'
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.third.kotlin
- - match: '"""'
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: '(\$\w+|\$\{[^\}]+\})'
- scope: variable.parameter.template.kotlin
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: '"'
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.double.kotlin
- - match: '"'
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: '(\$\w+|\$\{[^\}]+\})'
- scope: variable.parameter.template.kotlin
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: "'"
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.single.kotlin
- - match: "'"
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- - match: \\.
- scope: constant.character.escape.kotlin
- - match: "`"
- captures:
- 0: punctuation.definition.string.begin.kotlin
- push:
- - meta_scope: string.quoted.single.kotlin
- - match: "`"
- captures:
- 0: punctuation.definition.string.end.kotlin
- pop: true
- typedefs:
- - match: (?=\s*(?:type))
- push:
- - match: (?=$)
- pop: true
- - match: \b(type)\b
- scope: keyword.other.kotlin
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - include: expressions
- types:
- - match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
- scope: storage.type.buildin.kotlin
- - match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b
- scope: storage.type.buildin.array.kotlin
- - match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b
- captures:
- 1: storage.type.buildin.collection.kotlin
- push:
- - match: ">"
- pop: true
- - include: types
- - include: keywords
- - match: \w+<
- push:
- - match: ">"
- pop: true
- - include: types
- - include: keywords
- - match: '\{'
- push:
- - match: '\}'
- pop: true
- - include: statements
- - match: \(
- push:
- - match: \)
- pop: true
- - include: types
- - match: (->)
- scope: keyword.operator.declaration.kotlin
- variables:
- - match: (?=\s*\b(?:var|val)\b)
- push:
- - match: (?=:|=|(\b(by)\b)|$)
- pop: true
- - match: \b(var|val)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=:|=|(\b(by)\b)|$)
- pop: true
- - match: <
- push:
- - match: ">"
- pop: true
- - include: generics
- - match: '([\.<\?>\w]+\.)?(\w+)'
- captures:
- 2: entity.name.variable.kotlin
- - match: (:)
- captures:
- 1: keyword.operator.declaration.kotlin
- push:
- - match: (?==|$)
- pop: true
- - include: types
- - include: getters-and-setters
- - match: \b(by)\b
- captures:
- 1: keyword.other.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- - match: (=)
- captures:
- 1: keyword.operator.assignment.kotlin
- push:
- - match: (?=$)
- pop: true
- - include: expressions
- - include: getters-and-setters
diff --git a/tests/syntax-tests/highlighted/Kotlin/test.kt b/tests/syntax-tests/highlighted/Kotlin/test.kt
index dcb1560e..bb17aba2 100644
--- a/tests/syntax-tests/highlighted/Kotlin/test.kt
+++ b/tests/syntax-tests/highlighted/Kotlin/test.kt
@@ -1,85 +1,85 @@
-[38;2;249;38;114mimport[0m[38;2;248;248;242m kotlin.math.*[0m
+[38;2;249;38;114mimport[0m[38;2;248;248;242m [0m[38;2;248;248;242mkotlin[0m[38;2;248;248;242m.[0m[38;2;248;248;242mmath[0m[38;2;248;248;242m.[0m[38;2;249;38;114m*[0m
-[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mExample[0m[38;2;248;248;242m([0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m,[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mList[0m[38;2;248;248;242m<[0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m?>[0m
+[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mExample[0m[38;2;248;248;242m([0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m,[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mList[0m[38;2;248;248;242m<[0m[3;38;2;166;226;46mInt[0m[38;2;249;38;114m?[0m[38;2;248;248;242m>[0m
[38;2;248;248;242m)[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;166;226;46mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;248;248;242mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m
+[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m[38;2;248;248;242m {[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mtrue[0m
-[38;2;248;248;242m }[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;249;38;114m:[0m[38;2;248;248;242m Joke) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mby[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mjoke[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mnumber[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJoke[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;249;38;114mby[0m[38;2;248;248;242m [0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumber[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mcompanion [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mcompanion[0m[38;2;248;248;242m [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mSomeSingleton[0m
+[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSomeSingleton[0m
-[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mShape[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m
+[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mShape[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m sideLength[0m[38;2;249;38;114m.[0m[38;2;248;248;242mpow([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
+[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255msideLength[0m[38;2;248;248;242m.[0m[38;2;248;248;242mpow[0m[38;2;248;248;242m([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mPoint[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;190;132;255m0[0m
+[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mPoint[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m.0[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius[0m
-[38;2;248;248;242m }[0m
+[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;255;255;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m String.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
-[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m() {[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
+[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
[38;2;230;219;116m multiline[0m
[38;2;230;219;116m string[0m
[38;2;230;219;116m [0m
[38;2;230;219;116m some numbers: 123123 42[0m
-[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;249;38;114m.[0m[38;2;248;248;242mtrimIndent()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m Example(name [0m[38;2;249;38;114m=[0m[38;2;248;248;242m name, numbers [0m[38;2;249;38;114m=[0m[38;2;248;248;242m listOf([0m[38;2;190;132;255m512[0m[38;2;248;248;242m, [0m[38;2;190;132;255m42[0m[38;2;248;248;242m, [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m, [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m))[0m
+[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;248;248;242m.[0m[38;2;248;248;242mtrimIndent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mExample[0m[38;2;248;248;242m([0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumbers[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;190;132;255m512[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255m42[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m example[0m[38;2;249;38;114m.[0m[38;2;248;248;242mnumbers[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilterNotNull()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach { println(it) }[0m
+[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m.[0m[38;2;255;255;255mnumbers[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilterNotNull[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;255;255;255mit[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m setOf(Joke(), DelegatedJoke(Joke())[0m[38;2;249;38;114m.[0m[38;2;248;248;242mjoke)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilter(JokeInterface[0m[38;2;249;38;114m::[0m[38;2;248;248;242misFunny)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mmap(AbstractJoke[0m[38;2;249;38;114m::[0m[38;2;248;248;242mcontent)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEachIndexed { index[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m, joke [0m[38;2;249;38;114m->[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[3;38;2;253;151;31m${index + 1}[0m[38;2;230;219;116m): [0m[3;38;2;253;151;31m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242msetOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m[38;2;248;248;242m.[0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilter[0m[38;2;248;248;242m([0m[38;2;255;255;255mJokeInterface[0m[38;2;248;248;242m::[0m[38;2;248;248;242misFunny[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mmap[0m[38;2;248;248;242m([0m[38;2;255;255;255mAbstractJoke[0m[38;2;248;248;242m::[0m[38;2;248;248;242mcontent[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEachIndexed[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mindex[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mInt[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m->[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[38;2;248;248;242m${[0m[38;2;255;255;255mindex[0m[38;2;248;248;242m [0m[38;2;249;38;114m+[0m[38;2;248;248;242m [0m[38;2;190;132;255m1[0m[38;2;248;248;242m}[0m[38;2;230;219;116m): [0m[38;2;255;255;255m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m listOf(Square([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m), Point, Circle([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m))[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242massociateWith(Shape[0m[38;2;249;38;114m::[0m[38;2;248;248;242marea)[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mtoList()[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242msortedBy { it[0m[38;2;249;38;114m.[0m[38;2;248;248;242msecond }[0m
-[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach {[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[3;38;2;253;151;31m${it.first}[0m[38;2;230;219;116m: [0m[3;38;2;253;151;31m${it.second}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mSquare[0m[38;2;248;248;242m([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mPoint[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mCircle[0m[38;2;248;248;242m([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242massociateWith[0m[38;2;248;248;242m([0m[38;2;255;255;255mShape[0m[38;2;248;248;242m::[0m[38;2;248;248;242marea[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mtoList[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242msortedBy[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255mfirst[0m[38;2;248;248;242m}[0m[38;2;230;219;116m: [0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
+[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
-[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;249;38;114m.[0m[38;2;248;248;242mextensionMethod())[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m[38;2;248;248;242mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
-[38;2;248;248;242m require(SomeSingleton[0m[38;2;249;38;114m::[0m[38;2;248;248;242mclass[0m[38;2;249;38;114m.[0m[38;2;248;248;242msimpleName [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m) { [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m }[0m
+[38;2;248;248;242m [0m[38;2;248;248;242mrequire[0m[38;2;248;248;242m([0m[38;2;255;255;255mSomeSingleton[0m[38;2;248;248;242m::[0m[38;2;249;38;114mclass[0m[38;2;248;248;242m.[0m[38;2;255;255;255msimpleName[0m[38;2;248;248;242m [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
[38;2;248;248;242m}[0m
From 492c387ce7fdc02ea7c0aceabef3c2e1b15f09c4 Mon Sep 17 00:00:00 2001
From: guille
Date: Fri, 24 Apr 2026 20:52:52 +0200
Subject: [PATCH 061/130] CHANGELOG: reference PR not issue
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 341dbf42..637dc209 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,7 +55,7 @@
- Fixed manpage syntax so that ANSI escape codes don't get incorrectly highlighted and thus broken, see #3586 (@BlueElectivire)
- Map several Google Cloud CLI config files to their appropriate syntax #3635 (@victor-gp)
- Map all ignore dotfiles to Git Ignore syntax #3636 (@victor-gp)
-- Improved Kotlin syntax, see #3698 (@guille)
+- Improved Kotlin syntax, see #3699 (@guille)
## Themes
From de64d3a0ebe6f70637d215d6042e7eb9239755e8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Apr 2026 19:28:44 +0000
Subject: [PATCH 062/130] build(deps): bump assets/syntaxes/02_Extra/Idris2
Bumps [assets/syntaxes/02_Extra/Idris2](https://github.com/buzden/sublime-syntax-idris2) from `bbfe50e` to `4d8eb35`.
- [Commits](https://github.com/buzden/sublime-syntax-idris2/compare/bbfe50e023e0edc74f5e0c003eb946528d49279f...4d8eb35a38254d422030e77b4933530008dd3c6e)
---
updated-dependencies:
- dependency-name: assets/syntaxes/02_Extra/Idris2
dependency-version: 4d8eb35a38254d422030e77b4933530008dd3c6e
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
---
assets/syntaxes/02_Extra/Idris2 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/syntaxes/02_Extra/Idris2 b/assets/syntaxes/02_Extra/Idris2
index bbfe50e0..4d8eb35a 160000
--- a/assets/syntaxes/02_Extra/Idris2
+++ b/assets/syntaxes/02_Extra/Idris2
@@ -1 +1 @@
-Subproject commit bbfe50e023e0edc74f5e0c003eb946528d49279f
+Subproject commit 4d8eb35a38254d422030e77b4933530008dd3c6e
From ebf469b99af6c3d4eab784b2d6d29255062c0470 Mon Sep 17 00:00:00 2001
From: David Peter
Date: Thu, 30 Apr 2026 10:16:33 +0200
Subject: [PATCH 063/130] Update README
---
README.md | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/README.md b/README.md
index d6ef3381..e360a777 100644
--- a/README.md
+++ b/README.md
@@ -19,20 +19,6 @@
[Русский ]
-### Sponsors
-
-A special *thank you* goes to our biggest sponsors :
-
-
-
-
-
- Warp, the intelligent terminal
-
- Available on MacOS, Linux, Windows
-
-
-
### Syntax highlighting
`bat` supports syntax highlighting for a large number of programming and markup
From 10b4f07a87bb6bccd9d962bf9e7bb3f04b338128 Mon Sep 17 00:00:00 2001
From: David Peter
Date: Thu, 30 Apr 2026 10:18:58 +0200
Subject: [PATCH 064/130] Remove sponsors
---
README.md | 6 ------
doc/sponsors.md | 2 --
doc/sponsors/graphite-logo.jpeg | Bin 28195 -> 0 bytes
doc/sponsors/warp-logo.png | Bin 132621 -> 0 bytes
doc/sponsors/warp-pack-header.png | Bin 37674 -> 0 bytes
5 files changed, 8 deletions(-)
delete mode 100644 doc/sponsors/graphite-logo.jpeg
delete mode 100644 doc/sponsors/warp-logo.png
delete mode 100644 doc/sponsors/warp-pack-header.png
diff --git a/README.md b/README.md
index e360a777..375ec6d7 100644
--- a/README.md
+++ b/README.md
@@ -194,12 +194,6 @@ Note that the [Manpage syntax](assets/syntaxes/02_Extra/Manpage.sublime-syntax)
The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`.
-#### `Warp`
-
-
-
-
-
#### Highlighting `--help` messages
You can use `bat` to colorize help text: `$ cp --help | bat -plhelp`
diff --git a/doc/sponsors.md b/doc/sponsors.md
index 24509077..641e5205 100644
--- a/doc/sponsors.md
+++ b/doc/sponsors.md
@@ -10,5 +10,3 @@ No issue will have a different priority based on sponsorship status of the
reporter.
Contributions from anybody are most welcomed, please see our [`CONTRIBUTING.md`](../CONTRIBUTING.md) guide.
-
-If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).
diff --git a/doc/sponsors/graphite-logo.jpeg b/doc/sponsors/graphite-logo.jpeg
deleted file mode 100644
index 00443818dd32d97031c57dec5dcd83a9ad8777ab..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 28195
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
DCAoA}
diff --git a/doc/sponsors/warp-logo.png b/doc/sponsors/warp-logo.png
deleted file mode 100644
index f99dd38ceea805656daea5cd80c3525dbd307b71..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 132621
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
z