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
",
);
}