diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 9a44b831..5d2d27ae 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -173,7 +173,6 @@ jobs: - { target: i686-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-i686, use-cross: true } - { target: x86_64-apple-darwin , os: macos-13, } - { target: aarch64-apple-darwin , os: macos-14, } - - { target: x86_64-pc-windows-gnu , os: windows-2019, } - { target: x86_64-pc-windows-msvc , os: windows-2019, } - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: amd64, use-cross: true } - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-amd64, use-cross: true } diff --git a/.github/workflows/require-changelog-for-PRs.yml b/.github/workflows/require-changelog-for-PRs.yml index 9b9d7cde..346ed385 100644 --- a/.github/workflows/require-changelog-for-PRs.yml +++ b/.github/workflows/require-changelog-for-PRs.yml @@ -30,4 +30,4 @@ jobs: echo "Added lines in CHANGELOG.md:" echo "$ADDED" echo "Grepping for PR info (see CONTRIBUTING.md):" - grep "#${PR_NUMBER}\\b.*@${PR_SUBMITTER}\\b" <<< "$ADDED" + grep "#${PR_NUMBER}\\b.*${PR_SUBMITTER}\\b" <<< "$ADDED" diff --git a/.gitmodules b/.gitmodules index f51cea4f..2c096761 100644 --- a/.gitmodules +++ b/.gitmodules @@ -269,3 +269,6 @@ [submodule "assets/syntaxes/02_Extra/GDScript-sublime"] path = assets/syntaxes/02_Extra/GDScript-sublime url = https://github.com/beefsack/GDScript-sublime +[submodule "assets/syntaxes/02_Extra/sublime-odin"] + path = assets/syntaxes/02_Extra/sublime-odin + url = https://github.com/odin-lang/sublime-odin diff --git a/CHANGELOG.md b/CHANGELOG.md index 08503047..b2f9dd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,20 @@ ## Features +- Add paging to `--list-themes`, see PR #3239 (@einfachIrgendwer0815) + ## Bugfixes - Fix `BAT_THEME_DARK` and `BAT_THEME_LIGHT` being ignored, see issue #3171 and PR #3168 (@bash) - Prevent `--list-themes` from outputting default theme info to stdout when it is piped, see #3189 (@einfachIrgendwer0815) - Rename some submodules to fix Dependabot submodule updates, see issue #3198 and PR #3201 (@victor-gp) +- Make highlight tests fail when new syntaxes don't have fixtures PR #3255 (@dan-hipschman) +- Fix crash for multibyte characters in file path, see issue #3230 and PR #3245 (@HSM95) ## Other - Work around build failures when building `bat` from vendored sources #3179 (@dtolnay) +- CICD: Stop building for x86_64-pc-windows-gnu which fails #3261 (Enselic) ## Syntaxes @@ -23,6 +28,7 @@ - Map files with `csproj`, `vbproj`, `props` and `targets` extensions to XML syntax, see #3213 (@keith-hall) - Add debsources syntax to highlight `/etc/apt/sources.list` files, see #3215 (@keith-hall) - Add syntax test file for GDScript highlighting, see #3236 (@chetanjangir0) +- Add syntax test file for Odin highlighting, see #3241 (@chetanjangir0) ## Themes diff --git a/Cargo.lock b/Cargo.lock index a87351cd..5bb62706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "assert_cmd" @@ -153,6 +153,7 @@ dependencies = [ "terminal-colorsaurus", "thiserror 2.0.11", "toml", + "unicode-segmentation", "unicode-width 0.1.14", "wait-timeout", "walkdir", @@ -483,13 +484,13 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" dependencies = [ "cfg-if", "home", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -607,7 +608,7 @@ dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -844,9 +845,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1078,7 +1079,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1678,6 +1679,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -1812,7 +1819,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" dependencies = [ "windows-core 0.56.0", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1822,7 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1834,7 +1841,7 @@ dependencies = [ "windows-implement 0.56.0", "windows-interface 0.56.0", "windows-result", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1846,7 +1853,7 @@ dependencies = [ "windows-implement 0.57.0", "windows-interface 0.57.0", "windows-result", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1899,16 +1906,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -1917,7 +1915,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1926,22 +1924,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -1950,46 +1933,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2002,48 +1967,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 68172e72..2ee1b872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ version = "0.25.0" exclude = ["assets/syntaxes/*", "assets/themes/*"] build = "build/main.rs" edition = '2021' +# You are free to bump MSRV as soon as a reason for bumping emerges. rust-version = "1.74" [features] @@ -60,7 +61,7 @@ semver = "1.0" path_abs = { version = "0.5", default-features = false } clircle = { version = "0.6.1", default-features = false } bugreport = { version = "0.5.0", optional = true } -etcetera = { version = "0.8.0", optional = true } +etcetera = { version = "0.10.0", optional = true } grep-cli = { version = "0.1.11", optional = true } regex = { version = "1.10.6", optional = true } walkdir = { version = "2.5", optional = true } @@ -68,6 +69,7 @@ bytesize = { version = "1.3.0" } encoding_rs = "0.8.35" execute = { version = "0.2.13", optional = true } terminal-colorsaurus = "0.4" +unicode-segmentation = "1.12.0" [dependencies.git2] version = "0.20" @@ -101,8 +103,8 @@ serde = { version = "1.0", features = ["derive"] } nix = { version = "0.29", default-features = false, features = ["term"] } [build-dependencies] -anyhow = "1.0.86" -indexmap = { version = "2.3.0", features = ["serde"] } +anyhow = "1.0.97" +indexmap = { version = "2.8.0", features = ["serde"] } itertools = "0.14.0" once_cell = "1.20" regex = "1.10.6" diff --git a/assets/syntaxes/02_Extra/Org_mode b/assets/syntaxes/02_Extra/Org_mode index 4976d8f8..bb6e5d84 160000 --- a/assets/syntaxes/02_Extra/Org_mode +++ b/assets/syntaxes/02_Extra/Org_mode @@ -1 +1 @@ -Subproject commit 4976d8f84eeecd94df7da872bf404c125df04c73 +Subproject commit bb6e5d848151135ab8f87bdcb997437b2308718a diff --git a/assets/syntaxes/02_Extra/Zig b/assets/syntaxes/02_Extra/Zig index 1a4a3844..8a4a3fe4 160000 --- a/assets/syntaxes/02_Extra/Zig +++ b/assets/syntaxes/02_Extra/Zig @@ -1 +1 @@ -Subproject commit 1a4a38445fec495817625bafbeb01e79c44abcba +Subproject commit 8a4a3fe4a051f85c4752b82f586d395cab843c06 diff --git a/assets/syntaxes/02_Extra/sublime-odin b/assets/syntaxes/02_Extra/sublime-odin new file mode 160000 index 00000000..5d6a0ed4 --- /dev/null +++ b/assets/syntaxes/02_Extra/sublime-odin @@ -0,0 +1 @@ +Subproject commit 5d6a0ed41e41ec3709ec74f40686dc3761d6596e diff --git a/examples/buffer.rs b/examples/buffer.rs index 839689d4..eefdb249 100644 --- a/examples/buffer.rs +++ b/examples/buffer.rs @@ -1,4 +1,6 @@ -use bat::{assets::HighlightingAssets, config::Config, controller::Controller, Input}; +use bat::{ + assets::HighlightingAssets, config::Config, controller::Controller, output::OutputHandle, Input, +}; fn main() { let mut buffer = String::new(); @@ -10,7 +12,10 @@ fn main() { let controller = Controller::new(&config, &assets); let input = Input::from_file(file!()); controller - .run(vec![input.into()], Some(&mut buffer)) + .run( + vec![input.into()], + Some(OutputHandle::FmtWrite(&mut buffer)), + ) .unwrap(); println!("{buffer}"); diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index d339ba94..946ce5b1 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -124,7 +124,10 @@ impl App { // If we have -pp as an option when in auto mode, the pager should be disabled. if extra_plain || self.matches.get_flag("no-paging") { PagingMode::Never - } else if inputs.iter().any(Input::is_stdin) { + } else if inputs.iter().any(Input::is_stdin) + // ignore stdin when --list-themes is used because in that case no input will be read anyways + && !self.matches.get_flag("list-themes") + { // If we are reading from stdin, only enable paging if we write to an // interactive terminal and if we do not *read* from an interactive // terminal. diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index 70443b2f..4496032b 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -16,6 +16,7 @@ use std::io::{BufReader, Write}; use std::path::Path; use std::process; +use bat::output::{OutputHandle, OutputType}; use bat::theme::DetectColorScheme; use nu_ansi_term::Color::Green; use nu_ansi_term::Style; @@ -205,8 +206,9 @@ pub fn list_themes( config.language = Some("Rust"); config.style_components = StyleComponents(style); - let stdout = io::stdout(); - let mut stdout = stdout.lock(); + let mut output_type = + OutputType::from_mode(config.paging_mode, config.wrapping_mode, config.pager)?; + let mut writer = output_type.handle()?; let default_theme_name = default_theme(color_scheme(detect_color_scheme).unwrap_or_default()); for theme in assets.themes() { @@ -221,26 +223,29 @@ pub fn list_themes( }; if config.colored_output { writeln!( - stdout, + writer, "Theme: {}{}\n", Style::new().bold().paint(theme.to_string()), default_theme_info )?; config.theme = theme.to_string(); Controller::new(&config, &assets) - .run(vec![theme_preview_file()], None) + .run( + vec![theme_preview_file()], + Some(OutputHandle::IoWrite(&mut writer)), + ) .ok(); - writeln!(stdout)?; + writeln!(writer)?; } else if config.loop_through { - writeln!(stdout, "{theme}")?; + writeln!(writer, "{theme}")?; } else { - writeln!(stdout, "{theme}{default_theme_info}")?; + writeln!(writer, "{theme}{default_theme_info}")?; } } if config.colored_output { writeln!( - stdout, + writer, "Further themes can be installed to '{}', \ and are added to the cache with `bat cache --build`. \ For more information, see:\n\n \ diff --git a/src/controller.rs b/src/controller.rs index 422b381e..c8961b91 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -11,10 +11,10 @@ use crate::lessopen::LessOpenPreprocessor; #[cfg(feature = "git")] use crate::line_range::LineRange; use crate::line_range::{LineRanges, RangeCheckResult}; -use crate::output::OutputType; +use crate::output::{OutputHandle, OutputType}; #[cfg(feature = "paging")] use crate::paging::PagingMode; -use crate::printer::{InteractivePrinter, OutputHandle, Printer, SimplePrinter}; +use crate::printer::{InteractivePrinter, Printer, SimplePrinter}; use clircle::{Clircle, Identifier}; @@ -35,18 +35,14 @@ impl Controller<'_> { } } - pub fn run( - &self, - inputs: Vec, - output_buffer: Option<&mut dyn std::fmt::Write>, - ) -> Result { - self.run_with_error_handler(inputs, output_buffer, default_error_handler) + pub fn run(&self, inputs: Vec, output_handle: Option>) -> Result { + self.run_with_error_handler(inputs, output_handle, default_error_handler) } pub fn run_with_error_handler( &self, inputs: Vec, - output_buffer: Option<&mut dyn std::fmt::Write>, + output_handle: Option>, mut handle_error: impl FnMut(&Error, &mut dyn Write), ) -> Result { let mut output_type; @@ -88,8 +84,9 @@ impl Controller<'_> { clircle::Identifier::stdout() }; - let mut writer = match output_buffer { - Some(buf) => OutputHandle::FmtWrite(buf), + let mut writer = match output_handle { + Some(OutputHandle::FmtWrite(w)) => OutputHandle::FmtWrite(w), + Some(OutputHandle::IoWrite(w)) => OutputHandle::IoWrite(w), None => OutputHandle::IoWrite(output_type.handle()?), }; let mut no_errors: bool = true; diff --git a/src/lib.rs b/src/lib.rs index 502427a7..4c60f10e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ mod less; mod lessopen; pub mod line_range; pub(crate) mod nonprintable_notation; -mod output; +pub mod output; #[cfg(feature = "paging")] mod pager; #[cfg(feature = "paging")] diff --git a/src/output.rs b/src/output.rs index dc75d6e7..bb9a45d5 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::io::{self, Write}; #[cfg(feature = "paging")] use std::process::Child; @@ -162,3 +163,17 @@ impl Drop for OutputType { } } } + +pub enum OutputHandle<'a> { + IoWrite(&'a mut dyn io::Write), + FmtWrite(&'a mut dyn fmt::Write), +} + +impl OutputHandle<'_> { + pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { + match self { + Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into), + Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into), + } + } +} diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index a70ac021..4979bab5 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -10,6 +10,7 @@ use crate::{ error::Result, input, line_range::{HighlightedLineRanges, LineRange, LineRanges}, + output::OutputHandle, style::StyleComponent, StripAnsiMode, SyntaxMapping, WrappingMode, }; @@ -325,7 +326,10 @@ impl<'a> PrettyPrinter<'a> { // If writer is provided, pass it to the controller, otherwise pass None if let Some(mut w) = writer { - controller.run(inputs.into_iter().map(|i| i.into()).collect(), Some(&mut w)) + controller.run( + inputs.into_iter().map(|i| i.into()).collect(), + Some(OutputHandle::FmtWrite(&mut w)), + ) } else { controller.run(inputs.into_iter().map(|i| i.into()).collect(), None) } diff --git a/src/printer.rs b/src/printer.rs index 2c364bd7..dd2323cf 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,5 +1,3 @@ -use std::fmt; -use std::io; use std::vec::Vec; use nu_ansi_term::Color::{Fixed, Green, Red, Yellow}; @@ -17,6 +15,7 @@ use content_inspector::ContentType; use encoding_rs::{UTF_16BE, UTF_16LE}; +use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthChar; use crate::assets::{HighlightingAssets, SyntaxReferenceInSet}; @@ -29,6 +28,7 @@ use crate::diff::LineChanges; use crate::error::*; use crate::input::OpenedInput; use crate::line_range::RangeCheckResult; +use crate::output::OutputHandle; use crate::preprocessor::strip_ansi; use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::style::StyleComponent; @@ -68,20 +68,6 @@ const EMPTY_SYNTECT_STYLE: syntect::highlighting::Style = syntect::highlighting: font_style: FontStyle::empty(), }; -pub enum OutputHandle<'a> { - IoWrite(&'a mut dyn io::Write), - FmtWrite(&'a mut dyn fmt::Write), -} - -impl OutputHandle<'_> { - fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> { - match self { - Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into), - Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into), - } - } -} - pub(crate) trait Printer { fn print_header( &mut self, @@ -403,14 +389,18 @@ impl<'a> InteractivePrinter<'a> { handle: &mut OutputHandle, content: &str, ) -> Result<()> { - let mut content = content; let content_width = self.config.term_width - self.get_header_component_indent_length(); - while content.len() > content_width { - let (content_line, remaining) = content.split_at(content_width); - self.print_header_component_with_indent(handle, content_line)?; - content = remaining; + if content.chars().count() <= content_width { + return self.print_header_component_with_indent(handle, content); } - self.print_header_component_with_indent(handle, content) + + let mut content_graphemes: Vec<&str> = content.graphemes(true).collect(); + 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(); + } + self.print_header_component_with_indent(handle, content_graphemes.join("").as_str()) } fn highlight_regions_for_line<'b>( diff --git a/tests/examples/test.A—B가 b/tests/examples/test.A—B가 new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3aafb17c..97dbd550 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1600,6 +1600,17 @@ oken .stderr(""); } +#[test] +fn header_narrow_terminal_with_multibyte_chars() { + bat() + .arg("--terminal-width=30") + .arg("--decorations=always") + .arg("test.A—B가") + .assert() + .success() + .stderr(""); +} + #[test] #[cfg(feature = "git")] // Expected output assumes git is enabled fn header_default() { diff --git a/tests/syntax-tests/compare_highlighted_versions.py b/tests/syntax-tests/compare_highlighted_versions.py index 9cf67e46..ad142e9f 100755 --- a/tests/syntax-tests/compare_highlighted_versions.py +++ b/tests/syntax-tests/compare_highlighted_versions.py @@ -12,13 +12,15 @@ def compare_highlighted_versions(root_old, root_new): print(" -", root_old) print(" -", root_new) has_changes = False + # Used to check for newly added files that don't have a test + unknown_files = {strip_root(p) for p in glob.glob(path.join(root_new, "*", "*"))} + for path_old in glob.glob(path.join(root_old, "*", "*")): - filename = path.basename(path_old) - dirname = path.basename(path.dirname(path_old)) + rel_path = strip_root(path_old) + unknown_files.discard(rel_path) + path_new = path.join(root_new, rel_path) - path_new = path.join(root_new, dirname, filename) - - print("\n========== {}/{}".format(dirname, filename)) + print("\n========== {}".format(rel_path)) with open(path_old) as file_old: lines_old = file_old.readlines() @@ -39,11 +41,21 @@ def compare_highlighted_versions(root_old, root_new): has_changes = True else: print("No changes") - print() + for f in unknown_files: + print("\n========== {}: No fixture for this language, run update.sh".format(f)) + has_changes = True + + print() return has_changes +def strip_root(p: str) -> str: + filename = path.basename(p) + dirname = path.basename(path.dirname(p)) + return path.join(dirname, filename) + + if __name__ == "__main__": parser = argparse.ArgumentParser( description="This script compares two directories that were created " diff --git a/tests/syntax-tests/highlighted/Odin/test.odin b/tests/syntax-tests/highlighted/Odin/test.odin new file mode 100644 index 00000000..ab13ed8c --- /dev/null +++ b/tests/syntax-tests/highlighted/Odin/test.odin @@ -0,0 +1,27 @@ +package main + +import "core:fmt" +import "core:math" + +Vector :: struct { + components: []f64, +} + +euclidean_distance :: proc(v1: Vector, v2: Vector) -> f64 { + if len(v1.components) != len(v2.components) { + panic("Vectors must be same dimension") + } + sum: f64 = 0.0; + for i, comp in v1.components { + diff := comp - v2.components[i]; + sum += diff * diff; + } + return math.sqrt(sum); +} + +main :: proc() { + v1: Vector = Vector{components = []f64{1.0, 2.0, 3.0}}; + v2: Vector = Vector{components = []f64{4.0, 6.0, 8.0}}; + dist: f64 = euclidean_distance(v1, v2); + fmt.println("Distance:", dist); +} diff --git a/tests/syntax-tests/source/Odin/test.odin b/tests/syntax-tests/source/Odin/test.odin new file mode 100644 index 00000000..dafcfcfd --- /dev/null +++ b/tests/syntax-tests/source/Odin/test.odin @@ -0,0 +1,27 @@ +package main + +import "core:fmt" +import "core:math" + +Vector :: struct { + components: []f64, +} + +euclidean_distance :: proc(v1: Vector, v2: Vector) -> f64 { + if len(v1.components) != len(v2.components) { + panic("Vectors must be same dimension") + } + sum: f64 = 0.0; + for i, comp in v1.components { + diff := comp - v2.components[i]; + sum += diff * diff; + } + return math.sqrt(sum); +} + +main :: proc() { + v1: Vector = Vector{components = []f64{1.0, 2.0, 3.0}}; + v2: Vector = Vector{components = []f64{4.0, 6.0, 8.0}}; + dist: f64 = euclidean_distance(v1, v2); + fmt.println("Distance:", dist); +}