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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd6414e..99ad6c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,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/BE input #3369 (@keith-hall) ## Other 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" 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 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 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..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); @@ -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( @@ -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() @@ -354,8 +357,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/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/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/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/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 67bef3d8..7aa1ef20 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/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/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 b36204df..f807d125 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!( @@ -267,7 +267,9 @@ impl<'a> InputReader<'a> { }; if content_type == Some(ContentType::UTF_16LE) { - reader.read_until(0x00, &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,16 +285,44 @@ 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) { - let _ = self.inner.read_until(0x00, 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_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)?; + if n == 0 { + // EOF reached + break; + } + buf.extend_from_slice(&temp); + if buf.len() >= 2 + && buf[buf.len() - 2] == preceded_by_char + && buf[buf.len() - 1] == read_until_char + { + // end of line found + break; + } + // end of line not found, keep going + } + Ok(!buf.is_empty()) +} + #[test] fn basic() { let content = b"#!/bin/bash\necho hello"; @@ -350,3 +380,53 @@ 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\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\x0A\x00", + &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\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()); + assert!(buffer.is_empty()); +} 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..665871b4 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() { @@ -400,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()) } @@ -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/examples/test_UTF-16BE-complicated.txt b/tests/examples/test_UTF-16BE-complicated.txt new file mode 100644 index 00000000..f57ddeaa Binary files /dev/null and b/tests/examples/test_UTF-16BE-complicated.txt differ diff --git a/tests/examples/test_UTF-16BE.txt b/tests/examples/test_UTF-16BE.txt new file mode 100644 index 00000000..77a2bf2c Binary files /dev/null and b/tests/examples/test_UTF-16BE.txt differ diff --git a/tests/examples/test_UTF-16LE-complicated.txt b/tests/examples/test_UTF-16LE-complicated.txt new file mode 100644 index 00000000..b2eba9c9 Binary files /dev/null and b/tests/examples/test_UTF-16LE-complicated.txt differ diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e76a0e58..d9f8b403 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1452,6 +1452,38 @@ 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"); +} + +#[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 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(' ');