From 6c13a98f0197322b248e28609a8fdb54af0478ce Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:17:09 -0700 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20context=20support?= =?UTF-8?q?=20to=20line-range=20syntax?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(long-help.txt): 📚 add examples for N::C and N:M:C context syntax docs(clap_app.rs): 📚 update CLI help text with context syntax examples feat(line_range.rs): ✨ implement N::C and N:M:C parsing and add tests --- doc/long-help.txt | 2 ++ src/bin/bat/clap_app.rs | 4 ++- src/line_range.rs | 73 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/doc/long-help.txt b/doc/long-help.txt index 17d3395b..231b711e 100644 --- a/doc/long-help.txt +++ b/doc/long-help.txt @@ -194,6 +194,8 @@ Options: '--line-range 40:' prints lines 40 to the end of the file '--line-range 40' only prints line 40 '--line-range 30:+10' prints lines 30 to 40 + '--line-range 35::5' prints lines 30 to 40 (line 35 with 5 lines of context) + '--line-range 30:40:2' prints lines 28 to 42 (range 30-40 with 2 lines of context) -L, --list-languages Display a list of supported languages for syntax highlighting. diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index de2db078..67bef3d8 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -525,7 +525,9 @@ pub fn build_app(interactive_output: bool) -> Command { '--line-range :40' prints lines 1 to 40\n \ '--line-range 40:' prints lines 40 to the end of the file\n \ '--line-range 40' only prints line 40\n \ - '--line-range 30:+10' prints lines 30 to 40", + '--line-range 30:+10' prints lines 30 to 40\n \ + '--line-range 35::5' prints lines 30 to 40 (line 35 with 5 lines of context)\n \ + '--line-range 30:40:2' prints lines 28 to 42 (range 30-40 with 2 lines of context)", ), ) .arg( diff --git a/src/line_range.rs b/src/line_range.rs index a6ec22c2..b0d93594 100644 --- a/src/line_range.rs +++ b/src/line_range.rs @@ -98,8 +98,33 @@ impl LineRange { new_range.upper = RangeBound::Absolute(upper_absolute_bound); Ok(new_range) } + 3 => { + // Handle context syntax: N::C or N:M:C + if line_numbers[1].is_empty() { + // Format: N::C - single line with context + let line_number: usize = line_numbers[0].parse() + .map_err(|_| "Invalid line number in N::C format")?; + let context: usize = line_numbers[2].parse() + .map_err(|_| "Invalid context number in N::C format")?; + + new_range.lower = RangeBound::Absolute(line_number.saturating_sub(context)); + new_range.upper = RangeBound::Absolute(line_number.saturating_add(context)); + } else { + // Format: N:M:C - range with context + let start_line: usize = line_numbers[0].parse() + .map_err(|_| "Invalid start line number in N:M:C format")?; + let end_line: usize = line_numbers[1].parse() + .map_err(|_| "Invalid end line number in N:M:C format")?; + let context: usize = line_numbers[2].parse() + .map_err(|_| "Invalid context number in N:M:C format")?; + + new_range.lower = RangeBound::Absolute(start_line.saturating_sub(context)); + new_range.upper = RangeBound::Absolute(end_line.saturating_add(context)); + } + Ok(new_range) + } _ => Err( - "Line range contained more than one ':' character. Expected format: 'N' or 'N:M'" + "Line range contained too many ':' characters. Expected format: 'N', 'N:M', 'N::C', or 'N:M:C'" .into(), ), } @@ -274,6 +299,52 @@ fn test_parse_minus_fail() { assert!(range.is_err()); } +#[test] +fn test_parse_context_single_line() { + let range = LineRange::from("35::5").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(30), range.lower); + assert_eq!(RangeBound::Absolute(40), range.upper); +} + +#[test] +fn test_parse_context_range() { + let range = LineRange::from("30:40:2").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(28), range.lower); + assert_eq!(RangeBound::Absolute(42), range.upper); +} + +#[test] +fn test_parse_context_edge_cases() { + // Test with small line numbers that would underflow + let range = LineRange::from("5::10").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(0), range.lower); + assert_eq!(RangeBound::Absolute(15), range.upper); + + // Test with zero context + let range = LineRange::from("50::0").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(50), range.lower); + assert_eq!(RangeBound::Absolute(50), range.upper); + + // Test range with zero context + let range = LineRange::from("30:40:0").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(30), range.lower); + assert_eq!(RangeBound::Absolute(40), range.upper); +} + +#[test] +fn test_parse_context_fail() { + let range = LineRange::from("40::z"); + assert!(range.is_err()); + let range = LineRange::from("::5"); + assert!(range.is_err()); + let range = LineRange::from("40::"); + assert!(range.is_err()); + let range = LineRange::from("30:40:z"); + assert!(range.is_err()); + let range = LineRange::from("30::40:5"); + assert!(range.is_err()); +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RangeCheckResult { // Within one of the given ranges From dc2eae08a6fa5466376694dc278e3ccdc7f91269 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:25:28 -0700 Subject: [PATCH 2/8] =?UTF-8?q?docs:=20=F0=9F=93=9A=20update=20changelog?= =?UTF-8?q?=20for=20range=20context=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(CHANGELOG.md): 📚 add entry for context in line ranges and normalize list formatting style(src/line_range.rs): 🎨 trim trailing whitespace in context parsing code --- CHANGELOG.md | 88 +++++++++++++++++++++-------------------------- src/line_range.rs | 8 ++--- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ddf751..19aa2f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Add paging to `--list-themes`, see PR #3239 (@einfachIrgendwer0815) - Support negative relative line ranges, e.g. `bat -r :-10` / `bat -r='-10:'`, see #3068 (@ajesipow) +- Support context in line ranges, e.g. `bat -r 30::5` / `bat -r 30:40:5`, see #3344 (@cavanaug) ## Bugfixes @@ -189,7 +190,6 @@ - Add optional output_buffer arg to `Controller::run()` and `Controller::run_with_error_handler()`, see #2618 (@Piturnah) - # v0.23.0 ## Features @@ -227,7 +227,6 @@ - `PrettyPrinter::header` correctly displays a header with the filename, see #2378 and #2406 (@cstyles) - # v0.22.1 ## Bugfixes @@ -269,7 +268,6 @@ - Make `bat::PrettyPrinter::syntaxes()` iterate over new `bat::Syntax` struct instead of `&syntect::parsing::SyntaxReference`. See #2222 (@Enselic) - Clear highlights after printing, see #1919 and #1920 (@rhysd) - # v0.21.0 ## Features @@ -310,7 +308,6 @@ - Change `Error::SyntectError(syntect::LoadingError)` to `Error::SyntectError(syntect::Error)`. See #2181 (@Enselic) - Add `Error::SyntectLoadingError(syntect::LoadingError)` enum variant. See #2181 (@Enselic) - # v0.20.0 ## Features @@ -336,7 +333,6 @@ - Exposed `get_syntax_set` and `get_theme` methods on `HighlightingAssets`. See #2030 (@dandavison) - Added `HeaderFilename` and `HeaderFilesize` to `StyleComponent` enum, and mark it `#[non_exhaustive]`. See #1988 (@mdibaiee) - # v0.19.0 ## Performance @@ -391,14 +387,12 @@ - Replace the `error::Error(error::ErrorKind, _)` struct and enum with an `error::Error` enum. `Error(ErrorKind::UnknownSyntax, _)` becomes `Error::UnknownSyntax`, etc. Also remove the `error::ResultExt` trait. These changes stem from replacing `error-chain` with `thiserror`. See #1820 (@Enselic) - Add new `MappingTarget` enum variant `MapExtensionToUnknown`. Refer to its documentation for more information. Also mark `MappingTarget` as `#[non_exhaustive]` since more enum variants might be added in the future. See #1703 (@cbolgiano), #2012 (@Enselic) - # v0.18.3 ## Bugfixes - Bump `git2` dependency to fix build with Rust 1.54, see #1761 - # v0.18.2 ## Features @@ -422,7 +416,6 @@ - Added support for `XAML` syntax, see #1590 and #1655 (@mohamed-abdelnour) - Apply `DotENV` syntax also for `.env.default` and `.env.defaults` files, see #1669 - # v0.18.1 ## Bugfixes @@ -446,8 +439,6 @@ - Dark+ VS Code theme, see #1588 and #1598 (@PatriotRossii) - - # v0.18.0 ## Features @@ -490,11 +481,10 @@ ## `bat` as a library - The following `PrettyPrinter` methods have been removed (they were previously deprecated): - - `input_stdin_with_name` - - `input_from_bytes_with_name` - - `input_from_reader_with_name` - - `vcs_modification_markers` (if the `git` feature is not enabled) - + - `input_stdin_with_name` + - `input_from_bytes_with_name` + - `input_from_reader_with_name` + - `vcs_modification_markers` (if the `git` feature is not enabled) # v0.17.1 @@ -502,7 +492,6 @@ - Running `bat` without arguments fails ("output file is also an input"), see #1396 - # v0.17.0 ## Features @@ -531,7 +520,6 @@ - Coldark, see #1329 (@armandphilippot) - # v0.16.0 ## Features @@ -696,10 +684,12 @@ This introduces a `features = ["application"]` which is enabled by default and pulls in everything required by `bat` the application. When depending on bat as a library, downstream `Cargo.toml` should disable this feature to cut out inapplicable heavy dependencies: + ``` toml [dependencies] bat = { version = "0.14", default-features = false } ``` + Other optional functionality has also been put behind features: `paging` and `git` support. - Allow using the library with older syntect, see #896 and #898 (@dtolnay) @@ -708,7 +698,6 @@ - Rego, see #872 (@patrick-east) - Stylo, see #917 - # v0.13.0 ## `bat` as a library @@ -725,7 +714,7 @@ I want to stress that this is the very first release of the library. Things are That being said, you can start using it! See the example programs in [`examples/`](https://github.com/sharkdp/bat/tree/master/examples). -You can see the API documentation here: https://docs.rs/bat/ +You can see the API documentation here: ## Features @@ -734,6 +723,7 @@ You can see the API documentation here: https://docs.rs/bat/ present. The option now works like this: + ```bash --map-syntax : ``` @@ -741,8 +731,8 @@ You can see the API documentation here: https://docs.rs/bat/ For more information, see the `--help` text, the man page or the README. This new feature allows us to properly highlight files like: - * `/etc/profile` - * `~/.ssh/config` + - `/etc/profile` + - `~/.ssh/config` - `--highlight-line` now accepts line ranges, see #809 (@lkalir) - Proper wrapping support for output with wide Unicode characters, see #811 #787 and #815 (@Kogia-sima) @@ -1138,13 +1128,13 @@ You can see the API documentation here: https://docs.rs/bat/ ## Bugfixes -* Using `bat cache --init` leads to duplicated syntaxes, see #206 +- Using `bat cache --init` leads to duplicated syntaxes, see #206 ## Other -* Extended and cleaned-up `--help` text. -* Added initial version of a man page, see #52 -* New README sections: *Development* and *Troubleshooting*, see #220 +- Extended and cleaned-up `--help` text. +- Added initial version of a man page, see #52 +- New README sections: *Development* and *Troubleshooting*, see #220 # v0.5.0 @@ -1185,23 +1175,23 @@ You can see the API documentation here: https://docs.rs/bat/ ## Features -* Support for line-wrapping, see #54 and #102 (@eth-p) -* New and updated `--style` parameter, see #74 and README (@pitkley) -* Added `--theme` and `--list-themes` options, see #89 (@rleungx) -* Added syntax highlighting for: Julia (@iamed2), Dockerfiles, VimL, CMake, INI, Less -* Added a few popular Sublime Text highlighting themes, see #133 -* Support for bold, italic and underline font styles, see #96 -* Support for 32bit systems is now available, see #84 -* Added `-u` and `-n` options, see #134 -* ANSI color support on Windows 10 +- Support for line-wrapping, see #54 and #102 (@eth-p) +- New and updated `--style` parameter, see #74 and README (@pitkley) +- Added `--theme` and `--list-themes` options, see #89 (@rleungx) +- Added syntax highlighting for: Julia (@iamed2), Dockerfiles, VimL, CMake, INI, Less +- Added a few popular Sublime Text highlighting themes, see #133 +- Support for bold, italic and underline font styles, see #96 +- Support for 32bit systems is now available, see #84 +- Added `-u` and `-n` options, see #134 +- ANSI color support on Windows 10 ## Changes -* The customization folder for own syntaxes has been renamed from `syntax` to `syntaxes`, see README. -* Changed Markdown syntax to the default Sublime Text syntax, see #157 -* Sorted language listing (@rleungx) -* Command line arguments like `--theme` or `--color` can now override themselves. -* Improved `--help` text. +- The customization folder for own syntaxes has been renamed from `syntax` to `syntaxes`, see README. +- Changed Markdown syntax to the default Sublime Text syntax, see #157 +- Sorted language listing (@rleungx) +- Command line arguments like `--theme` or `--color` can now override themselves. +- Improved `--help` text. ## Bugfixes @@ -1223,24 +1213,24 @@ You can see the API documentation here: https://docs.rs/bat/ ## Features -* Automatic paging by integrating with `less`, see #29 (@BrainMaestro) -* Added support for reading from standard input, see #2 -* Added support for writing to non-interactive terminals (pipes, files, ..); new +- Automatic paging by integrating with `less`, see #29 (@BrainMaestro) +- Added support for reading from standard input, see #2 +- Added support for writing to non-interactive terminals (pipes, files, ..); new `--color=auto/always/never` option, see #26 (@BrainMaestro) -* Added `--list-languages` option to print all available syntaxes, see #69 (@connorkuehl) -* New option to specify the syntax via `-l`/`--language`, see #19 (@BrainMaestro) -* New option to control the output style (`--style`), see #5 (@nakulcg) -* Added syntax highlighting support for TOML files, see #37 +- Added `--list-languages` option to print all available syntaxes, see #69 (@connorkuehl) +- New option to specify the syntax via `-l`/`--language`, see #19 (@BrainMaestro) +- New option to control the output style (`--style`), see #5 (@nakulcg) +- Added syntax highlighting support for TOML files, see #37 ## Changes -* The `init-cache` sub-command has been removed. The cache can now be controlled via +- The `init-cache` sub-command has been removed. The cache can now be controlled via `bat cache`. See `bat cache -h` for all available commands. ## Bug fixes -* Get git repository from file path instead of current directory, see #22 (@nakulcg) -* Process substitution can now be used with bat (`bat <(echo a) <(echo b)`), see #80 +- Get git repository from file path instead of current directory, see #22 (@nakulcg) +- Process substitution can now be used with bat (`bat <(echo a) <(echo b)`), see #80 ## Thanks diff --git a/src/line_range.rs b/src/line_range.rs index b0d93594..f37c855b 100644 --- a/src/line_range.rs +++ b/src/line_range.rs @@ -106,7 +106,7 @@ impl LineRange { .map_err(|_| "Invalid line number in N::C format")?; let context: usize = line_numbers[2].parse() .map_err(|_| "Invalid context number in N::C format")?; - + new_range.lower = RangeBound::Absolute(line_number.saturating_sub(context)); new_range.upper = RangeBound::Absolute(line_number.saturating_add(context)); } else { @@ -117,7 +117,7 @@ impl LineRange { .map_err(|_| "Invalid end line number in N:M:C format")?; let context: usize = line_numbers[2].parse() .map_err(|_| "Invalid context number in N:M:C format")?; - + new_range.lower = RangeBound::Absolute(start_line.saturating_sub(context)); new_range.upper = RangeBound::Absolute(end_line.saturating_add(context)); } @@ -319,12 +319,12 @@ fn test_parse_context_edge_cases() { let range = LineRange::from("5::10").expect("Shouldn't fail on test!"); assert_eq!(RangeBound::Absolute(0), range.lower); assert_eq!(RangeBound::Absolute(15), range.upper); - + // Test with zero context let range = LineRange::from("50::0").expect("Shouldn't fail on test!"); assert_eq!(RangeBound::Absolute(50), range.lower); assert_eq!(RangeBound::Absolute(50), range.upper); - + // Test range with zero context let range = LineRange::from("30:40:0").expect("Shouldn't fail on test!"); assert_eq!(RangeBound::Absolute(30), range.lower); From bb8c5657e11f737406ce03d1a8bcd15b9c153e98 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:36:58 -0700 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=F0=9F=90=9B=20allow=20three-part=20?= =?UTF-8?q?range=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(src/line_range.rs): 🐛 support three-part (start:end:context) syntax, clamp lower at zero and extend upper, and add tests for invalid formats --- src/line_range.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/line_range.rs b/src/line_range.rs index f37c855b..3d15c607 100644 --- a/src/line_range.rs +++ b/src/line_range.rs @@ -235,14 +235,17 @@ fn test_parse_single() { #[test] fn test_parse_fail() { - let range = LineRange::from("40:50:80"); - assert!(range.is_err()); - let range = LineRange::from("40::80"); + // Test 4+ colon parts should still fail + let range = LineRange::from("40:50:80:90"); assert!(range.is_err()); + // Test invalid formats that should still fail let range = LineRange::from("-2:5"); assert!(range.is_err()); let range = LineRange::from(":40:"); assert!(range.is_err()); + // Test completely malformed input + let range = LineRange::from("abc:def"); + assert!(range.is_err()); } #[test] @@ -311,6 +314,11 @@ fn test_parse_context_range() { let range = LineRange::from("30:40:2").expect("Shouldn't fail on test!"); assert_eq!(RangeBound::Absolute(28), range.lower); assert_eq!(RangeBound::Absolute(42), range.upper); + + // Test the case that used to fail but should now work + let range = LineRange::from("40:50:80").expect("Shouldn't fail on test!"); + assert_eq!(RangeBound::Absolute(0), range.lower); // 40 - 80 = 0 (saturated) + assert_eq!(RangeBound::Absolute(130), range.upper); // 50 + 80 = 130 } #[test] From eadf15d9c076d6e15e45b85c8b381d6a28b68674 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:43:11 -0700 Subject: [PATCH 4/8] =?UTF-8?q?docs:=20=F0=9F=93=9A=20correct=20changelog?= =?UTF-8?q?=20PR=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs(CHANGELOG.md): 📚 correct PR number for context in line ranges entry from #3344 to #3345 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19aa2f6a..cc307d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Add paging to `--list-themes`, see PR #3239 (@einfachIrgendwer0815) - Support negative relative line ranges, e.g. `bat -r :-10` / `bat -r='-10:'`, see #3068 (@ajesipow) -- Support context in line ranges, e.g. `bat -r 30::5` / `bat -r 30:40:5`, see #3344 (@cavanaug) +- Support context in line ranges, e.g. `bat -r 30::5` / `bat -r 30:40:5`, see #3345 (@cavanaug) ## Bugfixes From b97c275de5dd82617136b0bfd5a2e390b33cd805 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:40:43 -0700 Subject: [PATCH 5/8] Restore original formatting that my editor decided to "autocorrect" --- CHANGELOG.md | 87 +++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc307d1d..43abf5b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -190,6 +190,7 @@ - Add optional output_buffer arg to `Controller::run()` and `Controller::run_with_error_handler()`, see #2618 (@Piturnah) + # v0.23.0 ## Features @@ -227,6 +228,7 @@ - `PrettyPrinter::header` correctly displays a header with the filename, see #2378 and #2406 (@cstyles) + # v0.22.1 ## Bugfixes @@ -268,6 +270,7 @@ - Make `bat::PrettyPrinter::syntaxes()` iterate over new `bat::Syntax` struct instead of `&syntect::parsing::SyntaxReference`. See #2222 (@Enselic) - Clear highlights after printing, see #1919 and #1920 (@rhysd) + # v0.21.0 ## Features @@ -308,6 +311,7 @@ - Change `Error::SyntectError(syntect::LoadingError)` to `Error::SyntectError(syntect::Error)`. See #2181 (@Enselic) - Add `Error::SyntectLoadingError(syntect::LoadingError)` enum variant. See #2181 (@Enselic) + # v0.20.0 ## Features @@ -333,6 +337,7 @@ - Exposed `get_syntax_set` and `get_theme` methods on `HighlightingAssets`. See #2030 (@dandavison) - Added `HeaderFilename` and `HeaderFilesize` to `StyleComponent` enum, and mark it `#[non_exhaustive]`. See #1988 (@mdibaiee) + # v0.19.0 ## Performance @@ -387,12 +392,14 @@ - Replace the `error::Error(error::ErrorKind, _)` struct and enum with an `error::Error` enum. `Error(ErrorKind::UnknownSyntax, _)` becomes `Error::UnknownSyntax`, etc. Also remove the `error::ResultExt` trait. These changes stem from replacing `error-chain` with `thiserror`. See #1820 (@Enselic) - Add new `MappingTarget` enum variant `MapExtensionToUnknown`. Refer to its documentation for more information. Also mark `MappingTarget` as `#[non_exhaustive]` since more enum variants might be added in the future. See #1703 (@cbolgiano), #2012 (@Enselic) + # v0.18.3 ## Bugfixes - Bump `git2` dependency to fix build with Rust 1.54, see #1761 + # v0.18.2 ## Features @@ -416,6 +423,7 @@ - Added support for `XAML` syntax, see #1590 and #1655 (@mohamed-abdelnour) - Apply `DotENV` syntax also for `.env.default` and `.env.defaults` files, see #1669 + # v0.18.1 ## Bugfixes @@ -439,6 +447,8 @@ - Dark+ VS Code theme, see #1588 and #1598 (@PatriotRossii) + + # v0.18.0 ## Features @@ -481,10 +491,11 @@ ## `bat` as a library - The following `PrettyPrinter` methods have been removed (they were previously deprecated): - - `input_stdin_with_name` - - `input_from_bytes_with_name` - - `input_from_reader_with_name` - - `vcs_modification_markers` (if the `git` feature is not enabled) + - `input_stdin_with_name` + - `input_from_bytes_with_name` + - `input_from_reader_with_name` + - `vcs_modification_markers` (if the `git` feature is not enabled) + # v0.17.1 @@ -492,6 +503,7 @@ - Running `bat` without arguments fails ("output file is also an input"), see #1396 + # v0.17.0 ## Features @@ -520,6 +532,7 @@ - Coldark, see #1329 (@armandphilippot) + # v0.16.0 ## Features @@ -684,12 +697,10 @@ This introduces a `features = ["application"]` which is enabled by default and pulls in everything required by `bat` the application. When depending on bat as a library, downstream `Cargo.toml` should disable this feature to cut out inapplicable heavy dependencies: - ``` toml [dependencies] bat = { version = "0.14", default-features = false } ``` - Other optional functionality has also been put behind features: `paging` and `git` support. - Allow using the library with older syntect, see #896 and #898 (@dtolnay) @@ -698,6 +709,7 @@ - Rego, see #872 (@patrick-east) - Stylo, see #917 + # v0.13.0 ## `bat` as a library @@ -714,7 +726,7 @@ I want to stress that this is the very first release of the library. Things are That being said, you can start using it! See the example programs in [`examples/`](https://github.com/sharkdp/bat/tree/master/examples). -You can see the API documentation here: +You can see the API documentation here: https://docs.rs/bat/ ## Features @@ -723,7 +735,6 @@ You can see the API documentation here: present. The option now works like this: - ```bash --map-syntax : ``` @@ -731,8 +742,8 @@ You can see the API documentation here: For more information, see the `--help` text, the man page or the README. This new feature allows us to properly highlight files like: - - `/etc/profile` - - `~/.ssh/config` + * `/etc/profile` + * `~/.ssh/config` - `--highlight-line` now accepts line ranges, see #809 (@lkalir) - Proper wrapping support for output with wide Unicode characters, see #811 #787 and #815 (@Kogia-sima) @@ -1128,13 +1139,13 @@ You can see the API documentation here: ## Bugfixes -- Using `bat cache --init` leads to duplicated syntaxes, see #206 +* Using `bat cache --init` leads to duplicated syntaxes, see #206 ## Other -- Extended and cleaned-up `--help` text. -- Added initial version of a man page, see #52 -- New README sections: *Development* and *Troubleshooting*, see #220 +* Extended and cleaned-up `--help` text. +* Added initial version of a man page, see #52 +* New README sections: *Development* and *Troubleshooting*, see #220 # v0.5.0 @@ -1175,23 +1186,23 @@ You can see the API documentation here: ## Features -- Support for line-wrapping, see #54 and #102 (@eth-p) -- New and updated `--style` parameter, see #74 and README (@pitkley) -- Added `--theme` and `--list-themes` options, see #89 (@rleungx) -- Added syntax highlighting for: Julia (@iamed2), Dockerfiles, VimL, CMake, INI, Less -- Added a few popular Sublime Text highlighting themes, see #133 -- Support for bold, italic and underline font styles, see #96 -- Support for 32bit systems is now available, see #84 -- Added `-u` and `-n` options, see #134 -- ANSI color support on Windows 10 +* Support for line-wrapping, see #54 and #102 (@eth-p) +* New and updated `--style` parameter, see #74 and README (@pitkley) +* Added `--theme` and `--list-themes` options, see #89 (@rleungx) +* Added syntax highlighting for: Julia (@iamed2), Dockerfiles, VimL, CMake, INI, Less +* Added a few popular Sublime Text highlighting themes, see #133 +* Support for bold, italic and underline font styles, see #96 +* Support for 32bit systems is now available, see #84 +* Added `-u` and `-n` options, see #134 +* ANSI color support on Windows 10 ## Changes -- The customization folder for own syntaxes has been renamed from `syntax` to `syntaxes`, see README. -- Changed Markdown syntax to the default Sublime Text syntax, see #157 -- Sorted language listing (@rleungx) -- Command line arguments like `--theme` or `--color` can now override themselves. -- Improved `--help` text. +* The customization folder for own syntaxes has been renamed from `syntax` to `syntaxes`, see README. +* Changed Markdown syntax to the default Sublime Text syntax, see #157 +* Sorted language listing (@rleungx) +* Command line arguments like `--theme` or `--color` can now override themselves. +* Improved `--help` text. ## Bugfixes @@ -1213,24 +1224,24 @@ You can see the API documentation here: ## Features -- Automatic paging by integrating with `less`, see #29 (@BrainMaestro) -- Added support for reading from standard input, see #2 -- Added support for writing to non-interactive terminals (pipes, files, ..); new +* Automatic paging by integrating with `less`, see #29 (@BrainMaestro) +* Added support for reading from standard input, see #2 +* Added support for writing to non-interactive terminals (pipes, files, ..); new `--color=auto/always/never` option, see #26 (@BrainMaestro) -- Added `--list-languages` option to print all available syntaxes, see #69 (@connorkuehl) -- New option to specify the syntax via `-l`/`--language`, see #19 (@BrainMaestro) -- New option to control the output style (`--style`), see #5 (@nakulcg) -- Added syntax highlighting support for TOML files, see #37 +* Added `--list-languages` option to print all available syntaxes, see #69 (@connorkuehl) +* New option to specify the syntax via `-l`/`--language`, see #19 (@BrainMaestro) +* New option to control the output style (`--style`), see #5 (@nakulcg) +* Added syntax highlighting support for TOML files, see #37 ## Changes -- The `init-cache` sub-command has been removed. The cache can now be controlled via +* The `init-cache` sub-command has been removed. The cache can now be controlled via `bat cache`. See `bat cache -h` for all available commands. ## Bug fixes -- Get git repository from file path instead of current directory, see #22 (@nakulcg) -- Process substitution can now be used with bat (`bat <(echo a) <(echo b)`), see #80 +* Get git repository from file path instead of current directory, see #22 (@nakulcg) +* Process substitution can now be used with bat (`bat <(echo a) <(echo b)`), see #80 ## Thanks From 67e3e42531722fb2b591b80f20bb3e646cea0bc7 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:18:21 -0700 Subject: [PATCH 6/8] =?UTF-8?q?test:=20=F0=9F=9A=A8=20extend=20line-range?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test(tests/examples/multiline.txt): 🚨 add lines 5-20 to sample file for expanded line-range tests test(tests/integration_tests.rs): 🚨 update expected outputs and add comprehensive context and error tests for line-range feature --- tests/examples/multiline.txt | 16 +++++ tests/integration_tests.rs | 118 +++++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/tests/examples/multiline.txt b/tests/examples/multiline.txt index 9c2a7090..c4352f8b 100644 --- a/tests/examples/multiline.txt +++ b/tests/examples/multiline.txt @@ -2,3 +2,19 @@ line 1 line 2 line 3 line 4 +line 5 +line 6 +line 7 +line 8 +line 9 +line 10 +line 11 +line 12 +line 13 +line 14 +line 15 +line 16 +line 17 +line 18 +line 19 +line 20 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0f570f89..213947bb 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -183,7 +183,7 @@ fn line_range_up_to_2_from_back() { .arg("--line-range=:-2") .assert() .success() - .stdout("line 1\nline 2\n"); + .stdout("line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\n"); } #[test] @@ -203,7 +203,7 @@ fn line_range_from_back_last_two() { .arg("--line-range=-2:") .assert() .success() - .stdout("line 3\nline 4\n"); + .stdout("line 19\nline 20\n"); } #[test] @@ -230,10 +230,10 @@ fn line_range_first_two() { fn line_range_last_3() { bat() .arg("multiline.txt") - .arg("--line-range=2:") + .arg("--line-range=18:") .assert() .success() - .stdout("line 2\nline 3\nline 4\n"); + .stdout("line 18\nline 19\nline 20\n"); } #[test] @@ -247,6 +247,116 @@ fn line_range_multiple() { .stdout("line 1\nline 2\nline 4\n"); } +#[test] +fn line_range_context_around_single_line() { + bat() + .arg("multiline.txt") + .arg("--line-range=10::2") + .assert() + .success() + .stdout("line 8\nline 9\nline 10\nline 11\nline 12\n"); +} + +#[test] +fn line_range_context_around_single_line_minimal() { + bat() + .arg("multiline.txt") + .arg("--line-range=5::1") + .assert() + .success() + .stdout("line 4\nline 5\nline 6\n"); +} + +#[test] +fn line_range_context_around_range() { + bat() + .arg("multiline.txt") + .arg("--line-range=10:12:2") + .assert() + .success() + .stdout("line 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\n"); +} + +#[test] +fn line_range_context_at_file_boundaries() { + bat() + .arg("multiline.txt") + .arg("--line-range=1::2") + .assert() + .success() + .stdout("line 1\nline 2\nline 3\n"); +} + +#[test] +fn line_range_context_at_end_of_file() { + bat() + .arg("multiline.txt") + .arg("--line-range=20::2") + .assert() + .success() + .stdout("line 18\nline 19\nline 20\n"); +} + +#[test] +fn line_range_context_zero() { + bat() + .arg("multiline.txt") + .arg("--line-range=10::0") + .assert() + .success() + .stdout("line 10\n"); +} + +#[test] +fn line_range_context_negative_single_line() { + bat() + .arg("multiline.txt") + .arg("--line-range=10::-1") + .assert() + .failure() + .stderr(predicate::str::contains("Invalid context number in N::C format")); +} + +#[test] +fn line_range_context_negative_range() { + bat() + .arg("multiline.txt") + .arg("--line-range=10:12:-1") + .assert() + .failure() + .stderr(predicate::str::contains("Invalid context number in N:M:C format")); +} + +#[test] +fn line_range_context_non_numeric_single_line() { + bat() + .arg("multiline.txt") + .arg("--line-range=10::abc") + .assert() + .failure() + .stderr(predicate::str::contains("Invalid context number in N::C format")); +} + +#[test] +fn line_range_context_non_numeric_range() { + bat() + .arg("multiline.txt") + .arg("--line-range=10:12:xyz") + .assert() + .failure() + .stderr(predicate::str::contains("Invalid context number in N:M:C format")); +} + +#[test] +fn line_range_context_very_large() { + bat() + .arg("multiline.txt") + .arg("--line-range=10::999999") + .assert() + .success() + .stdout("line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\n"); +} + #[test] fn squeeze_blank() { bat() From 58bfcd90514c8b32d11df6773c88ad8e1e315d79 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Wed, 13 Aug 2025 23:31:17 -0700 Subject: [PATCH 7/8] =?UTF-8?q?style:=20=F0=9F=8E=A8=20reformat=20predicat?= =?UTF-8?q?e=20assertions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style(integration_tests.rs): 🎨 doh, run cargo fmt --- tests/integration_tests.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 213947bb..7887785b 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -314,7 +314,9 @@ fn line_range_context_negative_single_line() { .arg("--line-range=10::-1") .assert() .failure() - .stderr(predicate::str::contains("Invalid context number in N::C format")); + .stderr(predicate::str::contains( + "Invalid context number in N::C format", + )); } #[test] @@ -324,7 +326,9 @@ fn line_range_context_negative_range() { .arg("--line-range=10:12:-1") .assert() .failure() - .stderr(predicate::str::contains("Invalid context number in N:M:C format")); + .stderr(predicate::str::contains( + "Invalid context number in N:M:C format", + )); } #[test] @@ -334,7 +338,9 @@ fn line_range_context_non_numeric_single_line() { .arg("--line-range=10::abc") .assert() .failure() - .stderr(predicate::str::contains("Invalid context number in N::C format")); + .stderr(predicate::str::contains( + "Invalid context number in N::C format", + )); } #[test] @@ -344,7 +350,9 @@ fn line_range_context_non_numeric_range() { .arg("--line-range=10:12:xyz") .assert() .failure() - .stderr(predicate::str::contains("Invalid context number in N:M:C format")); + .stderr(predicate::str::contains( + "Invalid context number in N:M:C format", + )); } #[test] From f79adaf607358e9dabedcaebd23d45f309731554 Mon Sep 17 00:00:00 2001 From: John Cavanaugh <59479+cavanaug@users.noreply.github.com> Date: Fri, 15 Aug 2025 05:57:41 -0700 Subject: [PATCH 8/8] =?UTF-8?q?test:=20=F0=9F=9A=A8=20update=20tests=20for?= =?UTF-8?q?=20truncated=20multiline.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test(tests/examples/multiline.txt): 🚨 trim sample file to 10 lines to match new behavior test(tests/integration_tests.rs): 🚨 adjust line ranges and expected outputs for 10-line sample; add multi-range context test --- tests/examples/multiline.txt | 10 ------- tests/integration_tests.rs | 51 +++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/tests/examples/multiline.txt b/tests/examples/multiline.txt index c4352f8b..fa2da6e5 100644 --- a/tests/examples/multiline.txt +++ b/tests/examples/multiline.txt @@ -8,13 +8,3 @@ line 7 line 8 line 9 line 10 -line 11 -line 12 -line 13 -line 14 -line 15 -line 16 -line 17 -line 18 -line 19 -line 20 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 7887785b..e76a0e58 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -163,7 +163,7 @@ fn line_numbers() { .arg("--decorations=always") .assert() .success() - .stdout(" 1 line 1\n 2 line 2\n 3 line 3\n 4 line 4\n"); + .stdout(" 1 line 1\n 2 line 2\n 3 line 3\n 4 line 4\n 5 line 5\n 6 line 6\n 7 line 7\n 8 line 8\n 9 line 9\n 10 line 10\n"); } #[test] @@ -183,7 +183,7 @@ fn line_range_up_to_2_from_back() { .arg("--line-range=:-2") .assert() .success() - .stdout("line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\n"); + .stdout("line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\n"); } #[test] @@ -203,7 +203,7 @@ fn line_range_from_back_last_two() { .arg("--line-range=-2:") .assert() .success() - .stdout("line 19\nline 20\n"); + .stdout("line 9\nline 10\n"); } #[test] @@ -230,10 +230,10 @@ fn line_range_first_two() { fn line_range_last_3() { bat() .arg("multiline.txt") - .arg("--line-range=18:") + .arg("--line-range=8:") .assert() .success() - .stdout("line 18\nline 19\nline 20\n"); + .stdout("line 8\nline 9\nline 10\n"); } #[test] @@ -247,14 +247,25 @@ fn line_range_multiple() { .stdout("line 1\nline 2\nline 4\n"); } +#[test] +fn line_range_multiple_with_context() { + bat() + .arg("multiline.txt") + .arg("--line-range=2::1") + .arg("--line-range=8::1") + .assert() + .success() + .stdout("line 1\nline 2\nline 3\nline 7\nline 8\nline 9\n"); +} + #[test] fn line_range_context_around_single_line() { bat() .arg("multiline.txt") - .arg("--line-range=10::2") + .arg("--line-range=5::2") .assert() .success() - .stdout("line 8\nline 9\nline 10\nline 11\nline 12\n"); + .stdout("line 3\nline 4\nline 5\nline 6\nline 7\n"); } #[test] @@ -271,10 +282,10 @@ fn line_range_context_around_single_line_minimal() { fn line_range_context_around_range() { bat() .arg("multiline.txt") - .arg("--line-range=10:12:2") + .arg("--line-range=4:6:2") .assert() .success() - .stdout("line 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\n"); + .stdout("line 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\n"); } #[test] @@ -291,27 +302,27 @@ fn line_range_context_at_file_boundaries() { fn line_range_context_at_end_of_file() { bat() .arg("multiline.txt") - .arg("--line-range=20::2") + .arg("--line-range=10::2") .assert() .success() - .stdout("line 18\nline 19\nline 20\n"); + .stdout("line 8\nline 9\nline 10\n"); } #[test] fn line_range_context_zero() { bat() .arg("multiline.txt") - .arg("--line-range=10::0") + .arg("--line-range=5::0") .assert() .success() - .stdout("line 10\n"); + .stdout("line 5\n"); } #[test] fn line_range_context_negative_single_line() { bat() .arg("multiline.txt") - .arg("--line-range=10::-1") + .arg("--line-range=5::-1") .assert() .failure() .stderr(predicate::str::contains( @@ -323,7 +334,7 @@ fn line_range_context_negative_single_line() { fn line_range_context_negative_range() { bat() .arg("multiline.txt") - .arg("--line-range=10:12:-1") + .arg("--line-range=5:6:-1") .assert() .failure() .stderr(predicate::str::contains( @@ -362,7 +373,9 @@ fn line_range_context_very_large() { .arg("--line-range=10::999999") .assert() .success() - .stdout("line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\nline 20\n"); + .stdout( + "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\n", + ); } #[test] @@ -1609,6 +1622,12 @@ fn snip() { 2 line 2 ...─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 8< ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 4 line 4 + 5 line 5 + 6 line 6 + 7 line 7 + 8 line 8 + 9 line 9 + 10 line 10 ", ); }