From 89f3d2d31afe5487c89a7d14b48717b56758f243 Mon Sep 17 00:00:00 2001 From: Claw Explorer Date: Thu, 26 Mar 2026 12:18:52 -0400 Subject: [PATCH 01/31] 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 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 @@ +<Solution> + <Folder Name="/Build/"> + <File Path="Directory.Build.props" /> + <File Path="projectname.targets" /> +  + <Project Path="console.csproj" /> + 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 12/31] 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 13/31] 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 14/31] 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 15/31] 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 16/31] 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 17/31] 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 18/31] [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 19/31] 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 20/31] 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 21/31] 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 22/31] 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 @@ +#!/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/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 @@ +#!/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/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 @@ +#!/usr/bin/wish +# Tk script detected via wish shebang +package require Tk +button .b -text "Click" -command {puts "clicked"} +pack .b 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 23/31] 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 24/31] 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 @@ -#!/usr/bin/expect -f -# Expect script detected via expect shebang -set timeout 30 -spawn ssh user@host -expect "password:" -send "secret\r" -expect eof +#!/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/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 @@ -#!/usr/bin/env tclsh -# Tcl script detected via tclsh shebang -puts "Hello from tclsh" -set x 42 -if {$x > 0} { - puts "positive" +#!/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/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 @@ -#!/usr/bin/wish -# Tk script detected via wish shebang -package require Tk -button .b -text "Click" -command {puts "clicked"} -pack .b +#!/usr/bin/wish +# Tk script detected via wish shebang +package require Tk +button .b -text "Click" -command {puts "clicked"} +pack .b 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 25/31] 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 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 26/31] 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 27/31] 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 28/31] 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 29/31] 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 @@ -import kotlin.math.* +import kotlin.math.* -data class Example( - val name: String, - val numbers: List<Int?> +data class Example( + val name: String, + val numbers: List<Int?> ) -fun interface JokeInterface { - fun isFunny(): Boolean +fun interface JokeInterface { + fun isFunny(): Boolean } -abstract class AbstractJoke : JokeInterface { - override fun isFunny() = false - abstract fun content(): String +abstract class AbstractJoke : JokeInterface { + override fun isFunny() = false + abstract fun content(): String } -class Joke : AbstractJoke() { - override fun isFunny(): Boolean { +class Joke : AbstractJoke() { + override fun isFunny(): Boolean {  return true - } - override fun content(): String = "content of joke here, haha" + } + override fun content(): String = "content of joke here, haha" } -class DelegatedJoke(val joke: Joke) : JokeInterface by joke { - val number: Long = 123L +class DelegatedJoke(val joke: Joke) : JokeInterface by joke { + val number: Long = 123L - companion object { - const val someConstant = "some constant text" - } + companion object { + const val someConstant = "some constant text" + } } -object SomeSingleton +object SomeSingleton -sealed class Shape { - abstract fun area(): Double +sealed class Shape { + abstract fun area(): Double } -data class Square(val sideLength: Double) : Shape() { - override fun area(): Double = sideLength.pow(2) +data class Square(val sideLength: Double) : Shape() { + override fun area(): Double = sideLength.pow(2) } -object Point : Shape() { - override fun area() = .0 +object Point : Shape() { + override fun area() = .0 } -class Circle(val radius: Double) : Shape() { - override fun area(): Double { - return PI * radius * radius - } +class Circle(val radius: Double) : Shape() { + override fun area(): Double { + return PI * radius * radius + } } -fun String.extensionMethod() = "test" +fun String.extensionMethod() = "test" -fun main() { - val name = """ +fun main() { + val name = """  multiline  string    some numbers: 123123 42 - """.trimIndent() - val example = Example(name = name, numbers = listOf(512, 42, null, -1)) + """.trimIndent() + val example = Example(name = name, numbers = listOf(512, 42, null, -1)) - example.numbers - .filterNotNull() - .forEach { println(it) } + example.numbers + .filterNotNull() + .forEach { println(it) } - setOf(Joke(), DelegatedJoke(Joke()).joke) - .filter(JokeInterface::isFunny) - .map(AbstractJoke::content) - .forEachIndexed { index: Int, joke -> - println("I heard a funny joke(#${index + 1}): $joke") - } + setOf(Joke(), DelegatedJoke(Joke()).joke) + .filter(JokeInterface::isFunny) + .map(AbstractJoke::content) + .forEachIndexed { index: Int, joke -> + println("I heard a funny joke(#${index + 1}): $joke") + } - listOf(Square(12.3), Point, Circle(5.2)) - .associateWith(Shape::area) - .toList() - .sortedBy { it.second } - .forEach { - println("${it.first}: ${it.second}") - } + listOf(Square(12.3), Point, Circle(5.2)) + .associateWith(Shape::area) + .toList() + .sortedBy { it.second } + .forEach { + println("${it.first}: ${it.second}") + } - println("some string".extensionMethod()) + println("some string".extensionMethod()) - require(SomeSingleton::class.simpleName == "SomeSingletonName") { "something does not seem right..." } + require(SomeSingleton::class.simpleName == "SomeSingletonName") { "something does not seem right..." } } From 492c387ce7fdc02ea7c0aceabef3c2e1b15f09c4 Mon Sep 17 00:00:00 2001 From: guille Date: Fri, 24 Apr 2026 20:52:52 +0200 Subject: [PATCH 30/31] 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 31/31] 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