From 94f1035c089cf4f5c8f9b52e51afa743b6d95c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 05:19:51 +0000 Subject: [PATCH 01/10] Bump nix from 0.29.0 to 0.30.1 Bumps [nix](https://github.com/nix-rust/nix) from 0.29.0 to 0.30.1. - [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md) - [Commits](https://github.com/nix-rust/nix/compare/v0.29.0...v0.30.1) --- updated-dependencies: - dependency-name: nix dependency-version: 0.30.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59dbd349..91960b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,9 +906,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libgit2-sys" @@ -996,9 +996,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.6.0", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 0b5187c8..dd54c0a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ tempfile = "3.16.0" serde = { version = "1.0", features = ["derive"] } [target.'cfg(unix)'.dev-dependencies] -nix = { version = "0.29", default-features = false, features = ["term"] } +nix = { version = "0.30", default-features = false, features = ["term"] } [build-dependencies] anyhow = "1.0.97" From 1bcf760d9088c5e85e5e68785da00c8ecde20e08 Mon Sep 17 00:00:00 2001 From: joshua! <49174473+skttlock@users.noreply.github.com> Date: Sat, 18 May 2024 14:54:45 -0700 Subject: [PATCH 02/10] docs: Include GNOME bash script for Dark Mode Title. I developed this on Fedora 39 with GNOME. Tested and works on my setup as expected. --- README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bcd6d46..ecdf83d2 100644 --- a/README.md +++ b/README.md @@ -711,7 +711,7 @@ sidebar. Calling `bat` with `--tabs=0` will override it and let tabs be consumed ### Dark mode -If you make use of the dark mode feature in macOS, you might want to configure `bat` to use a different +If you make use of the dark mode feature in **macOS**, you might want to configure `bat` to use a different theme based on the OS theme. The following snippet uses the `default` theme when in the _dark mode_ and the `GitHub` theme when in the _light mode_. @@ -719,6 +719,34 @@ and the `GitHub` theme when in the _light mode_. alias cat="bat --theme auto:system --theme-dark default --theme-light GitHub" ``` +The same dark mode feature is now available in **GNOME** and affects the `org.gnome.desktop.interface color-scheme` setting. The following code converts the above to use said setting. + +```bash +# .bashrc +sys_color_scheme_is_dark() { + condition=$(gsettings get org.gnome.desktop.interface color-scheme) + condition=$(echo "$condition" | tr -d "[:space:]'") + if [ $condition == "prefer-dark" ]; then + return 0 + else + return 1 + fi +} + +bat_alias_wrapper() { + #get color scheme + sys_color_scheme_is_dark + if [[ $? -eq 0 ]]; then + # bat command with dark color scheme + bat --theme=default "$@" + else + # bat command with light color scheme + bat --theme=GitHub "$@" + fi +} +alias cat='bat_alias_wrapper' +``` + ## Configuration file From 76e6a49a2e32620a2bdf60c68b9f06a614219edb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 06:02:13 +0000 Subject: [PATCH 03/10] Bump assets/syntaxes/02_Extra/Fish from `98316d4` to `ef510fd` Bumps [assets/syntaxes/02_Extra/Fish](https://github.com/Phidica/sublime-fish) from `98316d4` to `ef510fd`. - [Release notes](https://github.com/Phidica/sublime-fish/releases) - [Commits](https://github.com/Phidica/sublime-fish/compare/98316d4332936f74babb51cb56161410ae9d6e2c...ef510fd7592186d3c7f6aa066986c047ec29fe81) --- updated-dependencies: - dependency-name: assets/syntaxes/02_Extra/Fish dependency-version: ef510fd7592186d3c7f6aa066986c047ec29fe81 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- assets/syntaxes/02_Extra/Fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/syntaxes/02_Extra/Fish b/assets/syntaxes/02_Extra/Fish index 98316d43..ef510fd7 160000 --- a/assets/syntaxes/02_Extra/Fish +++ b/assets/syntaxes/02_Extra/Fish @@ -1 +1 @@ -Subproject commit 98316d4332936f74babb51cb56161410ae9d6e2c +Subproject commit ef510fd7592186d3c7f6aa066986c047ec29fe81 From 66751534605863a9e4588c470e98198ac1bfdc11 Mon Sep 17 00:00:00 2001 From: Keith Hall Date: Thu, 7 Aug 2025 23:31:15 +0300 Subject: [PATCH 04/10] Fix the read_line method for utf16le input to determine the end of the line, instead of reading until \n (0x0A) and then reading until 0x00 and calling it done, read until we find 0x00 preceded by 0x0A. --- CHANGELOG.md | 1 + src/input.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3481f3..a1c2b775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Add missing mappings for various bash/zsh files, see PR #3262 (@AdamGaskins) - Send all bat errors to stderr by default, see #3336 (@JerryImMouse) - Make --map-syntax target case insensitive to match --language, see #3206 (@keith-hall) +- Correctly determine the end of the line in UTF16LE input #3369 (@keith-hall) ## Other diff --git a/src/input.rs b/src/input.rs index b36204df..e5f7e4d6 100644 --- a/src/input.rs +++ b/src/input.rs @@ -267,7 +267,7 @@ impl<'a> InputReader<'a> { }; if content_type == Some(ContentType::UTF_16LE) { - reader.read_until(0x00, &mut first_line).ok(); + read_utf16le_line(&mut reader, &mut first_line).ok(); } InputReader { @@ -286,13 +286,31 @@ impl<'a> InputReader<'a> { let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?; if self.content_type == Some(ContentType::UTF_16LE) { - let _ = self.inner.read_until(0x00, buf); + return read_utf16le_line(&mut self.inner, buf); } Ok(res) } } +fn read_utf16le_line(reader: &mut R, buf: &mut Vec) -> io::Result { + loop { + let mut temp = Vec::new(); + let n = reader.read_until(0x00, &mut temp)?; + if n == 0 { + // EOF reached + break; + } + buf.extend_from_slice(&temp); + if buf.len() >= 2 && buf[buf.len() - 2] == 0x0A && buf[buf.len() - 1] == 0x00 { + // end of line found + break; + } + // end of line not found, keep going + } + return Ok(!buf.is_empty()); +} + #[test] fn basic() { let content = b"#!/bin/bash\necho hello"; @@ -350,3 +368,28 @@ fn utf16le() { assert!(!res.unwrap()); assert!(buffer.is_empty()); } + +#[test] +fn utf16le_issue3367() { + let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52"; + let mut reader = InputReader::new(&content[..]); + + assert_eq!( + b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52", + &reader.first_line[..] + ); + + let mut buffer = vec![]; + + let res = reader.read_line(&mut buffer); + assert!(res.is_ok()); + assert!(res.unwrap()); + assert_eq!(b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52", &buffer[..]); + + buffer.clear(); + + let res = reader.read_line(&mut buffer); + assert!(res.is_ok()); + assert!(!res.unwrap()); + assert!(buffer.is_empty()); +} From 40c4c8e5420e86d9baac9cbd9438d7584ad9da86 Mon Sep 17 00:00:00 2001 From: Keith Hall Date: Sat, 16 Aug 2025 15:33:30 +0300 Subject: [PATCH 05/10] More thorough tests for UTF16LE --- src/input.rs | 22 ++++++++++++++++--- tests/examples/test_UTF-16BE.txt | Bin 0 -> 56 bytes tests/examples/test_UTF-16LE-complicated.txt | Bin 0 -> 50 bytes tests/integration_tests.rs | 20 +++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/examples/test_UTF-16BE.txt create mode 100644 tests/examples/test_UTF-16LE-complicated.txt diff --git a/src/input.rs b/src/input.rs index e5f7e4d6..69b10906 100644 --- a/src/input.rs +++ b/src/input.rs @@ -371,11 +371,13 @@ fn utf16le() { #[test] fn utf16le_issue3367() { - let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52"; + let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00\ + \x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00\ + \x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00"; let mut reader = InputReader::new(&content[..]); assert_eq!( - b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52", + b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00", &reader.first_line[..] ); @@ -384,10 +386,24 @@ fn utf16le_issue3367() { let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(res.unwrap()); - assert_eq!(b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52", &buffer[..]); + assert_eq!(b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00", &buffer[..]); buffer.clear(); + let res = reader.read_line(&mut buffer); + assert!(res.is_ok()); + assert!(res.unwrap()); + assert_eq!(b"\x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00", &buffer[..]); + + buffer.clear(); + + let res = reader.read_line(&mut buffer); + assert!(res.is_ok()); + assert!(res.unwrap()); + assert_eq!(b"\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00", &buffer[..]); + + buffer.clear(); + let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(!res.unwrap()); diff --git a/tests/examples/test_UTF-16BE.txt b/tests/examples/test_UTF-16BE.txt new file mode 100644 index 0000000000000000000000000000000000000000..77a2bf2c42cc13f081dac3623529c7dcb74746c0 GIT binary patch literal 56 xcmezOpCN-Gl_3WR^BEKv%7J7Nke$N7#ZUs2$z&)7ibBXlAgctZwirlq0RVhs3xfav literal 0 HcmV?d00001 diff --git a/tests/examples/test_UTF-16LE-complicated.txt b/tests/examples/test_UTF-16LE-complicated.txt new file mode 100644 index 0000000000000000000000000000000000000000..b2eba9c94e337d573a698625e8ee56abc6d150f9 GIT binary patch literal 50 xcmezWkIRq2kISDSh>IbOAs+}87?Kzg8HyOV7%~`A8FGLSBvuY2i-7DD1^^~r3RnOD literal 0 HcmV?d00001 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0f570f89..35a85623 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1321,6 +1321,26 @@ fn utf16() { .assert() .success() .stdout("hello world\n"); + + bat() + .arg("--plain") + .arg("--decorations=always") + .arg("test_UTF-16BE.txt") + .assert() + .success() + .stdout("hello world\nthis is a test\n"); +} + +#[test] +fn utf16le() { + bat() + .arg("--decorations=always") + .arg("--style=numbers") + .arg("--color=never") + .arg("test_UTF-16LE-complicated.txt") + .assert() + .success() + .stdout(" 1 上一伊刀\n 2 foo bar\n 3 hello world\n"); } // Regression test for https://github.com/sharkdp/bat/issues/1922 From 96ce80d0e27eee0ca8d7fe25f0526465d38a2a54 Mon Sep 17 00:00:00 2001 From: Keith Hall Date: Sat, 16 Aug 2025 15:33:53 +0300 Subject: [PATCH 06/10] Apply same fix and tests for UTF16BE --- CHANGELOG.md | 2 +- src/input.rs | 20 +++++++++++-------- tests/examples/test_UTF-16BE-complicated.txt | Bin 0 -> 50 bytes tests/integration_tests.rs | 12 +++++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 tests/examples/test_UTF-16BE-complicated.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index a1c2b775..69338d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Add missing mappings for various bash/zsh files, see PR #3262 (@AdamGaskins) - Send all bat errors to stderr by default, see #3336 (@JerryImMouse) - Make --map-syntax target case insensitive to match --language, see #3206 (@keith-hall) -- Correctly determine the end of the line in UTF16LE input #3369 (@keith-hall) +- Correctly determine the end of the line in UTF16LE/BE input #3369 (@keith-hall) ## Other diff --git a/src/input.rs b/src/input.rs index 69b10906..3abfdd82 100644 --- a/src/input.rs +++ b/src/input.rs @@ -267,7 +267,9 @@ impl<'a> InputReader<'a> { }; if content_type == Some(ContentType::UTF_16LE) { - read_utf16le_line(&mut reader, &mut first_line).ok(); + read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok(); + } else if content_type == Some(ContentType::UTF_16BE) { + read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok(); } InputReader { @@ -283,26 +285,28 @@ impl<'a> InputReader<'a> { return Ok(true); } - let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?; - if self.content_type == Some(ContentType::UTF_16LE) { - return read_utf16le_line(&mut self.inner, buf); + return read_utf16_line(&mut self.inner, buf, 0x00, 0x0A); + } + if self.content_type == Some(ContentType::UTF_16BE) { + return read_utf16_line(&mut self.inner, buf, 0x0A, 0x00); } + let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?; Ok(res) } } -fn read_utf16le_line(reader: &mut R, buf: &mut Vec) -> io::Result { +fn read_utf16_line(reader: &mut R, buf: &mut Vec, read_until_char: u8, preceded_by_char: u8) -> io::Result { loop { let mut temp = Vec::new(); - let n = reader.read_until(0x00, &mut temp)?; + let n = reader.read_until(read_until_char, &mut temp)?; if n == 0 { // EOF reached break; } buf.extend_from_slice(&temp); - if buf.len() >= 2 && buf[buf.len() - 2] == 0x0A && buf[buf.len() - 1] == 0x00 { + if buf.len() >= 2 && buf[buf.len() - 2] == preceded_by_char && buf[buf.len() - 1] == read_until_char { // end of line found break; } @@ -403,7 +407,7 @@ fn utf16le_issue3367() { assert_eq!(b"\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00", &buffer[..]); buffer.clear(); - + let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(!res.unwrap()); diff --git a/tests/examples/test_UTF-16BE-complicated.txt b/tests/examples/test_UTF-16BE-complicated.txt new file mode 100644 index 0000000000000000000000000000000000000000..f57ddeaacc3dfcdbd72b525b127c01c5068bb3fd GIT binary patch literal 50 wcmezO-;c|W!JjLLfq{!5jUgWh6&R8j5*dns{0xRvh8!RSiIoG%A|N{j03dw|SO5S3 literal 0 HcmV?d00001 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 35a85623..7579794d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1343,6 +1343,18 @@ fn utf16le() { .stdout(" 1 上一伊刀\n 2 foo bar\n 3 hello world\n"); } +#[test] +fn utf16be() { + bat() + .arg("--decorations=always") + .arg("--style=numbers") + .arg("--color=never") + .arg("test_UTF-16BE-complicated.txt") + .assert() + .success() + .stdout(" 1 上一伊刀\n 2 foo bar\n 3 hello world\n"); +} + // Regression test for https://github.com/sharkdp/bat/issues/1922 #[test] fn bom_not_stripped_in_loop_through_mode() { From bdaf25793d258f3c5609412a834912e7ec91ecb5 Mon Sep 17 00:00:00 2001 From: Keith Hall Date: Sat, 16 Aug 2025 15:35:13 +0300 Subject: [PATCH 07/10] cargo fmt --- src/input.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/input.rs b/src/input.rs index 3abfdd82..f9cfac2a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -297,7 +297,12 @@ impl<'a> InputReader<'a> { } } -fn read_utf16_line(reader: &mut R, buf: &mut Vec, read_until_char: u8, preceded_by_char: u8) -> io::Result { +fn read_utf16_line( + reader: &mut R, + buf: &mut Vec, + read_until_char: u8, + preceded_by_char: u8, +) -> io::Result { loop { let mut temp = Vec::new(); let n = reader.read_until(read_until_char, &mut temp)?; @@ -306,7 +311,10 @@ fn read_utf16_line(reader: &mut R, buf: &mut Vec, read_until_cha break; } buf.extend_from_slice(&temp); - if buf.len() >= 2 && buf[buf.len() - 2] == preceded_by_char && buf[buf.len() - 1] == read_until_char { + if buf.len() >= 2 + && buf[buf.len() - 2] == preceded_by_char + && buf[buf.len() - 1] == read_until_char + { // end of line found break; } @@ -390,21 +398,30 @@ fn utf16le_issue3367() { let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(res.unwrap()); - assert_eq!(b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00", &buffer[..]); + assert_eq!( + b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00", + &buffer[..] + ); buffer.clear(); let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(res.unwrap()); - assert_eq!(b"\x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00", &buffer[..]); + assert_eq!( + b"\x6F\x00\x20\x00\x62\x00\x61\x00\x72\x00\x0A\x00", + &buffer[..] + ); buffer.clear(); let res = reader.read_line(&mut buffer); assert!(res.is_ok()); assert!(res.unwrap()); - assert_eq!(b"\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00", &buffer[..]); + assert_eq!( + b"\x68\x00\x65\x00\x6C\x00\x6C\x00\x6F\x00\x20\x00\x77\x00\x6F\x00\x72\x00\x6C\x00\x64\x00", + &buffer[..] + ); buffer.clear(); From d9fbd185412b313272d0b90ba39d3c40643e22b4 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Thu, 27 Feb 2025 00:30:05 -0500 Subject: [PATCH 08/10] inline format arguments In a few cases, removed the unneeded `&` - this causes a minor slowdown because compiler cannot eliminate those (yet). --- assets/theme_preview.rs | 2 +- src/assets.rs | 7 +++---- src/assets/build_assets.rs | 14 ++++---------- src/bin/bat/assets.rs | 2 +- src/bin/bat/clap_app.rs | 2 +- src/bin/bat/config.rs | 3 +-- src/bin/bat/main.rs | 9 ++++----- src/error.rs | 6 ++---- src/input.rs | 4 ++-- src/lessopen.rs | 4 ++-- src/printer.rs | 14 ++++++-------- src/vscreen.rs | 6 +++--- tests/tester/mod.rs | 2 +- tests/utils/mocked_pagers.rs | 2 +- 14 files changed, 32 insertions(+), 45 deletions(-) diff --git a/assets/theme_preview.rs b/assets/theme_preview.rs index 1e436225..a3e4b31f 100644 --- a/assets/theme_preview.rs +++ b/assets/theme_preview.rs @@ -1,5 +1,5 @@ // Output the square of a number. fn print_square(num: f64) { let result = f64::powf(num, 2.0); - println!("The square of {:.2} is {:.2}.", num, result); + println!("The square of {num:.2} is {result:.2}."); } diff --git a/src/assets.rs b/src/assets.rs index ed6f27e9..bc8ae16c 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -191,11 +191,11 @@ impl HighlightingAssets { Some(theme) => theme, None => { if theme == "ansi-light" || theme == "ansi-dark" { - bat_warning!("Theme '{}' is deprecated, using 'ansi' instead.", theme); + bat_warning!("Theme '{theme}' is deprecated, using 'ansi' instead."); return self.get_theme("ansi"); } if !theme.is_empty() { - bat_warning!("Unknown theme '{}', using default.", theme) + bat_warning!("Unknown theme '{theme}', using default.") } self.get_theme_set() .get( @@ -354,8 +354,7 @@ fn asset_from_cache( ) -> Result { let contents = fs::read(path).map_err(|_| { format!( - "Could not load cached {} '{}'", - description, + "Could not load cached {description} '{}'", path.to_string_lossy() ) })?; diff --git a/src/assets/build_assets.rs b/src/assets/build_assets.rs index 5406c2f7..6d9c8e59 100644 --- a/src/assets/build_assets.rs +++ b/src/assets/build_assets.rs @@ -47,9 +47,8 @@ fn build_theme_set(source_dir: &Path, include_integrated_assets: bool) -> Result let res = theme_set.add_from_folder(&theme_dir); if let Err(err) = res { println!( - "Failed to load one or more themes from '{}' (reason: '{}')", + "Failed to load one or more themes from '{}' (reason: '{err}')", theme_dir.to_string_lossy(), - err, ); } } else { @@ -162,15 +161,10 @@ fn asset_to_cache( description: &str, compressed: bool, ) -> Result<()> { - print!("Writing {} to {} ... ", description, path.to_string_lossy()); + print!("Writing {description} to {} ... ", path.to_string_lossy()); let contents = asset_to_contents(asset, description, compressed)?; - std::fs::write(path, &contents[..]).map_err(|_| { - format!( - "Could not save {} to {}", - description, - path.to_string_lossy() - ) - })?; + std::fs::write(path, &contents[..]) + .map_err(|_| format!("Could not save {description} to {}", path.to_string_lossy()))?; println!("okay"); Ok(()) } diff --git a/src/bin/bat/assets.rs b/src/bin/bat/assets.rs index 0eeb5bf1..7f917088 100644 --- a/src/bin/bat/assets.rs +++ b/src/bin/bat/assets.rs @@ -50,7 +50,7 @@ fn clear_asset(path: PathBuf, description: &str) { println!("skipped (not present)"); } Err(err) => { - println!("could not remove the cache file {:?}: {}", &path, err); + println!("could not remove the cache file {path:?}: {err}"); } Ok(_) => println!("okay"), } diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index de2db078..d417d4a7 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -16,7 +16,7 @@ static VERSION: Lazy = Lazy::new(|| { if git_version.is_empty() { crate_version!().to_string() } else { - format!("{} ({})", crate_version!(), git_version) + format!("{} ({git_version})", crate_version!()) } }); diff --git a/src/bin/bat/config.rs b/src/bin/bat/config.rs index a0ee7ba3..7972d808 100644 --- a/src/bin/bat/config.rs +++ b/src/bin/bat/config.rs @@ -88,9 +88,8 @@ pub fn generate_config_file() -> bat::error::Result<()> { fs::write(&config_file, default_config).map_err(|e| { format!( - "Failed to create config file at '{}': {}", + "Failed to create config file at '{}': {e}", config_file.to_string_lossy(), - e ) })?; diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index 4496032b..1a7b9056 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -162,7 +162,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result { }; for lang in languages { - write!(result, "{:width$}{}", lang.name, separator, width = longest).ok(); + write!(result, "{:width$}{separator}", lang.name, width = longest).ok(); // Number of characters on this line so far, wrap before `desired_width` let mut num_chars = 0; @@ -173,7 +173,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result { let new_chars = word.len() + comma_separator.len(); if num_chars + new_chars >= desired_width { num_chars = 0; - write!(result, "\n{:width$}{}", "", separator, width = longest).ok(); + write!(result, "\n{:width$}{separator}", "", width = longest).ok(); } num_chars += new_chars; @@ -224,9 +224,8 @@ pub fn list_themes( if config.colored_output { writeln!( writer, - "Theme: {}{}\n", + "Theme: {}{default_theme_info}\n", Style::new().bold().paint(theme.to_string()), - default_theme_info )?; config.theme = theme.to_string(); Controller::new(&config, &assets) @@ -363,7 +362,7 @@ fn run() -> Result { "fish" => println!("{}", completions::FISH_COMPLETION), "ps1" => println!("{}", completions::PS1_COMPLETION), "zsh" => println!("{}", completions::ZSH_COMPLETION), - _ => unreachable!("No completion for shell '{}' available.", shell), + _ => unreachable!("No completion for shell '{shell}' available."), } return Ok(true); } diff --git a/src/error.rs b/src/error.rs index e5db81cc..0b1d3f95 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,18 +60,16 @@ pub fn default_error_handler(error: &Error, output: &mut dyn Write) { Error::SerdeYamlError(_) => { writeln!( output, - "{}: Error while parsing metadata.yaml file: {}", + "{}: Error while parsing metadata.yaml file: {error}", Red.paint("[bat error]"), - error ) .ok(); } _ => { writeln!( &mut std::io::stderr().lock(), - "{}: {}", + "{}: {error}", Red.paint("[bat error]"), - error ) .ok(); } diff --git a/src/input.rs b/src/input.rs index f9cfac2a..880d095e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -217,14 +217,14 @@ impl<'a> Input<'a> { metadata: self.metadata, reader: { let mut file = File::open(&path) - .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?; + .map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?; if file.metadata()?.is_dir() { return Err(format!("'{}' is a directory.", path.to_string_lossy()).into()); } if let Some(stdout) = stdout_identifier { let input_identifier = Identifier::try_from(file).map_err(|e| { - format!("{}: Error identifying file: {}", path.to_string_lossy(), e) + format!("{}: Error identifying file: {e}", path.to_string_lossy()) })?; if stdout.surely_conflicts_with(&input_identifier) { return Err(format!( diff --git a/src/lessopen.rs b/src/lessopen.rs index 79f977af..966bc2c0 100644 --- a/src/lessopen.rs +++ b/src/lessopen.rs @@ -39,7 +39,7 @@ impl LessOpenPreprocessor { // Note that $LESSCLOSE has no such requirement if lessopen.match_indices("%s").count() != 1 { let error_msg = "LESSOPEN ignored: must contain exactly one %s"; - bat_warning!("{}", error_msg); + bat_warning!("{error_msg}"); return Err(error_msg.into()); } @@ -110,7 +110,7 @@ impl LessOpenPreprocessor { if self.preprocess_stdin { if let Some(stdout) = stdout_identifier { let input_identifier = Identifier::try_from(clircle::Stdio::Stdin) - .map_err(|e| format!("Stdin: Error identifying file: {}", e))?; + .map_err(|e| format!("Stdin: Error identifying file: {e}"))?; if stdout.surely_conflicts_with(&input_identifier) { return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into()); } diff --git a/src/printer.rs b/src/printer.rs index 369431ef..5b68e18e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -329,7 +329,7 @@ impl<'a> InteractivePrinter<'a> { self.print_horizontal_line_term(handle, self.colors.grid)?; } else { let hline = "─".repeat(self.config.term_width - (self.panel_width + 1)); - let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline); + let hline = format!("{}{grid_char}{hline}", "─".repeat(self.panel_width)); writeln!(handle, "{}", self.colors.grid.paint(hline))?; } @@ -343,8 +343,7 @@ impl<'a> InteractivePrinter<'a> { let text_truncated: String = text.chars().take(self.panel_width - 1).collect(); let text_filled: String = format!( - "{}{}", - text_truncated, + "{text_truncated}{}", " ".repeat(self.panel_width - 1 - text_truncated.len()) ); if self.config.style_components.grid() { @@ -513,13 +512,12 @@ impl Printer for InteractivePrinter<'_> { .try_for_each(|component| match component { StyleComponent::HeaderFilename => { let header_filename = format!( - "{}{}{}", + "{}{}{mode}", description .kind() .map(|kind| format!("{kind}: ")) .unwrap_or_else(|| "".into()), self.colors.header_value.paint(description.title()), - mode ); self.print_header_multiline_component(handle, &header_filename) } @@ -701,7 +699,7 @@ impl Printer for InteractivePrinter<'_> { "{}{}", as_terminal_escaped( style, - &format!("{}{}", self.ansi_style, text_trimmed), + &format!("{}{text_trimmed}", self.ansi_style), true_color, colored_output, italics, @@ -791,7 +789,7 @@ impl Printer for InteractivePrinter<'_> { "{}{}\n{}", as_terminal_escaped( style, - &format!("{}{}", self.ansi_style, line_buf), + &format!("{}{line_buf}", self.ansi_style), self.config.true_color, self.config.colored_output, self.config.use_italic_text, @@ -818,7 +816,7 @@ impl Printer for InteractivePrinter<'_> { "{}", as_terminal_escaped( style, - &format!("{}{}", self.ansi_style, line_buf), + &format!("{}{line_buf}", self.ansi_style), self.config.true_color, self.config.colored_output, self.config.use_italic_text, diff --git a/src/vscreen.rs b/src/vscreen.rs index 06cc038a..3ea3aec8 100644 --- a/src/vscreen.rs +++ b/src/vscreen.rs @@ -212,15 +212,15 @@ impl Attributes { } fn update_with_charset(&mut self, kind: char, set: impl Iterator) -> bool { - self.charset = format!("\x1B{}{}", kind, set.take(1).collect::()); + self.charset = format!("\x1B{kind}{}", set.take(1).collect::()); true } fn parse_color(color: u16, parameters: &mut dyn Iterator) -> String { match color % 10 { 8 => match parameters.next() { - Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)), - Some(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)), + Some(5) /* 256-color */ => format!("\x1B[{color};5;{}m", join(";", 1, parameters)), + Some(2) /* 24-bit color */ => format!("\x1B[{color};2;{}m", join(";", 3, parameters)), Some(c) => format!("\x1B[{color};{c}m"), _ => "".to_owned(), }, diff --git a/tests/tester/mod.rs b/tests/tester/mod.rs index 91fa40df..6c7e4226 100644 --- a/tests/tester/mod.rs +++ b/tests/tester/mod.rs @@ -75,7 +75,7 @@ fn create_sample_directory() -> Result { // Copy over `sample.rs` let sample_path = temp_dir.path().join("sample.rs"); - println!("{:?}", &sample_path); + println!("{sample_path:?}"); fs::copy("tests/snapshots/sample.rs", &sample_path).expect("successful copy"); // Commit diff --git a/tests/utils/mocked_pagers.rs b/tests/utils/mocked_pagers.rs index 98055e38..ae6f4a32 100644 --- a/tests/utils/mocked_pagers.rs +++ b/tests/utils/mocked_pagers.rs @@ -19,7 +19,7 @@ fn get_mocked_pagers_dir() -> PathBuf { pub fn from(base: &str) -> String { let mut cmd_and_args = shell_words::split(base).unwrap(); let suffix = if cfg!(windows) { ".bat" } else { "" }; - let mut out_cmd = format!("{}{}", cmd_and_args.first().unwrap(), suffix); + let mut out_cmd = format!("{}{suffix}", cmd_and_args.first().unwrap()); if (cmd_and_args.len() > 1) { out_cmd.push(' '); From 503c50b1ec477e8aecc77b6f474f893ef7b6930b Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 18 Aug 2025 20:06:01 -0400 Subject: [PATCH 09/10] chore: address all `cargo clippy` lints * also do a bit of a doc cleanup for the `load_from_folder` fn --- src/assets.rs | 19 +++++++++++-------- src/assets/assets_metadata.rs | 18 +++++++++--------- src/bin/bat/app.rs | 4 ++-- src/bin/bat/input.rs | 2 +- src/input.rs | 2 +- src/printer.rs | 2 +- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/assets.rs b/src/assets.rs index bc8ae16c..82c160c9 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -152,7 +152,7 @@ impl HighlightingAssets { &self, path: impl AsRef, mapping: &SyntaxMapping, - ) -> Result { + ) -> Result> { let path = path.as_ref(); let syntax_match = mapping.get_syntax_for(path); @@ -212,7 +212,7 @@ impl HighlightingAssets { language: Option<&str>, input: &mut OpenedInput, mapping: &SyntaxMapping, - ) -> Result { + ) -> Result> { if let Some(language) = language { let syntax_set = self.get_syntax_set()?; return syntax_set @@ -244,14 +244,17 @@ impl HighlightingAssets { pub(crate) fn find_syntax_by_name( &self, syntax_name: &str, - ) -> Result> { + ) -> Result>> { let syntax_set = self.get_syntax_set()?; Ok(syntax_set .find_syntax_by_name(syntax_name) .map(|syntax| SyntaxReferenceInSet { syntax, syntax_set })) } - fn find_syntax_by_extension(&self, e: Option<&OsStr>) -> Result> { + fn find_syntax_by_extension( + &self, + e: Option<&OsStr>, + ) -> Result>> { let syntax_set = self.get_syntax_set()?; let extension = e.and_then(|x| x.to_str()).unwrap_or_default(); Ok(syntax_set @@ -259,7 +262,7 @@ impl HighlightingAssets { .map(|syntax| SyntaxReferenceInSet { syntax, syntax_set })) } - fn find_syntax_by_token(&self, token: &str) -> Result> { + fn find_syntax_by_token(&self, token: &str) -> Result>> { let syntax_set = self.get_syntax_set()?; Ok(syntax_set .find_syntax_by_token(token) @@ -270,7 +273,7 @@ impl HighlightingAssets { &self, file_name: &OsStr, ignored_suffixes: &IgnoredSuffixes, - ) -> Result> { + ) -> Result>> { let mut syntax = self.find_syntax_by_extension(Some(file_name))?; if syntax.is_none() { syntax = @@ -286,7 +289,7 @@ impl HighlightingAssets { &self, file_name: &OsStr, ignored_suffixes: &IgnoredSuffixes, - ) -> Result> { + ) -> Result>> { let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?; if syntax.is_none() { syntax = @@ -301,7 +304,7 @@ impl HighlightingAssets { fn get_first_line_syntax( &self, reader: &mut InputReader, - ) -> Result> { + ) -> Result>> { let syntax_set = self.get_syntax_set()?; Ok(String::from_utf8(reader.first_line.clone()) .ok() diff --git a/src/assets/assets_metadata.rs b/src/assets/assets_metadata.rs index cfc7a9e0..63b0531a 100644 --- a/src/assets/assets_metadata.rs +++ b/src/assets/assets_metadata.rs @@ -40,15 +40,15 @@ impl AssetsMetadata { /// Load metadata about the stored cache file from the given folder. /// /// There are several possibilities: - /// - We find a metadata.yaml file and are able to parse it - /// => return the contained information - /// - We find a metadata.yaml file and but are not able to parse it - /// => return a SerdeYamlError - /// - We do not find a metadata.yaml file but a syntaxes.bin or themes.bin file - /// => assume that these were created by an old version of bat and return - /// AssetsMetadata::default() without version information - /// - We do not find a metadata.yaml file and no cached assets - /// => no user provided assets are available, return None + /// - We find a `metadata.yaml` file and are able to parse it + /// - return the contained information + /// - We find a `metadata.yaml` file, but are not able to parse it + /// - return a [`Error::SerdeYamlError`] + /// - We do not find a `metadata.yaml` file but a `syntaxes.bin` or `themes.bin` file + /// - assume that these were created by an old version of bat and return + /// [`AssetsMetadata::default()`] without version information + /// - We do not find a `metadata.yaml` file and no cached assets + /// - no user provided assets are available, return `None` pub fn load_from_folder(path: &Path) -> Result> { match Self::try_load_from_folder(path) { Ok(metadata) => Ok(Some(metadata)), diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 0474e120..b3d48cba 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -96,7 +96,7 @@ impl App { Ok(clap_app::build_app(interactive_output).get_matches_from(args)) } - pub fn config(&self, inputs: &[Input]) -> Result { + pub fn config(&self, inputs: &[Input]) -> Result> { let style_components = self.style_components()?; let extra_plain = self.matches.get_count("plain") > 1; @@ -338,7 +338,7 @@ impl App { }) } - pub fn inputs(&self) -> Result> { + pub fn inputs(&self) -> Result>> { let filenames: Option> = self .matches .get_many::("file-name") diff --git a/src/bin/bat/input.rs b/src/bin/bat/input.rs index 3c928906..a64001f2 100644 --- a/src/bin/bat/input.rs +++ b/src/bin/bat/input.rs @@ -5,7 +5,7 @@ pub fn new_file_input<'a>(file: &'a Path, name: Option<&'a Path>) -> Input<'a> { named(Input::ordinary_file(file), name.or(Some(file))) } -pub fn new_stdin_input(name: Option<&Path>) -> Input { +pub fn new_stdin_input(name: Option<&Path>) -> Input<'_> { named(Input::stdin(), name) } diff --git a/src/input.rs b/src/input.rs index 880d095e..f807d125 100644 --- a/src/input.rs +++ b/src/input.rs @@ -320,7 +320,7 @@ fn read_utf16_line( } // end of line not found, keep going } - return Ok(!buf.is_empty()); + Ok(!buf.is_empty()) } #[test] diff --git a/src/printer.rs b/src/printer.rs index 5b68e18e..665871b4 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -399,7 +399,7 @@ impl<'a> InteractivePrinter<'a> { while content_graphemes.len() > content_width { let (content_line, remaining) = content_graphemes.split_at(content_width); self.print_header_component_with_indent(handle, content_line.join("").as_str())?; - content_graphemes = remaining.iter().cloned().collect(); + content_graphemes = remaining.to_vec(); } self.print_header_component_with_indent(handle, content_graphemes.join("").as_str()) } From caf6fa369fef13d90b0eb413db6ec1dfe1576550 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 19 Aug 2025 01:04:18 -0400 Subject: [PATCH 10/10] chore: add CI linting * ensure `cargo clippy` treats all suggestions as errors in CI * ensure `cargo clippy` runs with the latest stable Rust because Clippy is MSRV-aware, so it will not suggest anything unapplicable. Also note that whenever new release comes out, Clippy might temporarily show new errors - I think this is better than forgetting to update the version somewhere, and keep running older tools. * Update actions/checkout to the latest v5 --- .github/workflows/CICD.yml | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index ba37ec52..9ee0dce4 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest needs: - crate_metadata - - ensure_cargo_fmt + - lint - min_version - license_checks - test_with_new_syntaxes_and_themes @@ -35,7 +35,7 @@ jobs: name: Extract crate metadata runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Extract crate information id: crate_metadata run: | @@ -51,15 +51,16 @@ jobs: homepage: ${{ steps.crate_metadata.outputs.homepage }} msrv: ${{ steps.crate_metadata.outputs.msrv }} - ensure_cargo_fmt: - name: Ensure 'cargo fmt' has been run + lint: + name: Ensure code quality runs-on: ubuntu-latest steps: - uses: dtolnay/rust-toolchain@stable with: - components: rustfmt - - uses: actions/checkout@v4 + components: rustfmt,clippy + - uses: actions/checkout@v5 - run: cargo fmt -- --check + - run: cargo clippy --locked --all-targets --all-features -- -D warnings min_version: name: Minimum supported rust version @@ -67,15 +68,11 @@ jobs: needs: crate_metadata steps: - name: Checkout source code - uses: actions/checkout@v4 - + uses: actions/checkout@v5 - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) uses: dtolnay/rust-toolchain@master with: toolchain: ${{ needs.crate_metadata.outputs.msrv }} - components: clippy - - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) - run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} - name: Run tests run: cargo test --locked ${{ env.MSRV_FEATURES }} @@ -83,7 +80,7 @@ jobs: name: License checks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: true # we especially want to perform license checks on submodules - run: tests/scripts/license-checks.sh @@ -93,7 +90,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true # we need all syntax and theme submodules - name: Install Rust toolchain @@ -122,7 +119,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Prepare environment variables run: | echo "BAT_SYSTEM_CONFIG_PREFIX=$GITHUB_WORKSPACE/tests/examples/system_config" >> $GITHUB_ENV @@ -138,7 +135,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Check documentation @@ -153,7 +150,7 @@ jobs: runs-on: ubuntu-latest steps: - run: cargo install cargo-audit --locked - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: cargo audit build: @@ -181,7 +178,7 @@ jobs: BUILD_CMD: cargo steps: - name: Checkout source code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install prerequisites shell: bash