Compare commits

...

17 Commits

Author SHA1 Message Date
einfachIrgendwer0815 d0bb0ba06e
Fix spacing issue with git feature disabled 2024-05-05 10:16:10 +02:00
einfachIrgendwer0815 1e52d10752
Add CHANGELOG entry for line limits 2024-05-05 10:16:07 +02:00
einfachIrgendwer0815 e34f073f7b
Test line limits 2024-05-05 10:14:44 +02:00
einfachIrgendwer0815 9594b0e4aa
Make line limits cli configurable 2024-05-05 10:10:31 +02:00
einfachIrgendwer0815 8c1d7b18db
Limit line buffer length
Discards long lines to prevent out-of-memory events.
2024-05-05 10:10:28 +02:00
dependabot[bot] b4e3a84e1a
Bump serde from 1.0.197 to 1.0.199 (#2953)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.197 to 1.0.199.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.197...v1.0.199)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 03:23:25 +00:00
dependabot[bot] f7c39e8353
Bump os_str_bytes from 6.6.1 to 7.0.0 (#2952)
Bumps [os_str_bytes](https://github.com/dylni/os_str_bytes) from 6.6.1 to 7.0.0.
- [Release notes](https://github.com/dylni/os_str_bytes/releases)
- [Commits](https://github.com/dylni/os_str_bytes/compare/6.6.1...7.0.0)

---
updated-dependencies:
- dependency-name: os_str_bytes
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 03:10:09 +00:00
dependabot[bot] 37d9f0533c
Bump indexmap from 2.2.2 to 2.2.6 (#2950)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.2.2 to 2.2.6.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.2.2...2.2.6)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 02:47:11 +00:00
dependabot[bot] d560f2a515
Bump serde_with from 3.7.0 to 3.8.1 (#2949)
Bumps [serde_with](https://github.com/jonasbb/serde_with) from 3.7.0 to 3.8.1.
- [Release notes](https://github.com/jonasbb/serde_with/releases)
- [Commits](https://github.com/jonasbb/serde_with/compare/v3.7.0...v3.8.1)

---
updated-dependencies:
- dependency-name: serde_with
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 02:27:04 +00:00
Stéphane Blondon bb4d1cbd2e refactor: factorize constants by inverting loop and condition order 2024-04-19 11:44:47 +02:00
Stéphane Blondon 23ec433167 display which theme is the default one in basic output 2024-04-19 11:44:47 +02:00
Sharun 9eaed3e3f0
[JavaScript] Support bun in shebang for syntax highlighting (#2913)
* [JavaScript] Support bun in shebang for syntax highlighting

---------

Co-authored-by: Keith Hall <keith-hall@users.noreply.github.com>
2024-04-15 06:17:41 +00:00
sblondon d5bd4aa93f
display which theme is the default one in colored output (#2838) 2024-04-14 15:54:52 +02:00
Keith Hall 66b70dd8ed
Merge pull request #2933 from vorburger/patch-1
Fix minor typo in GitHub Syntax Request Issue Template
2024-04-09 21:35:45 +03:00
Michael Vorburger 01731478a6
Fix minor typo in GitHub Syntax Request Issue Template 2024-04-09 19:54:46 +02:00
Rivera Calzadillas f8c5429a6c Print $TERM with --diagnostic 2024-04-07 14:24:16 +02:00
Rivera Calzadillas f71226adbb Sort env vars printed by --diagnostic 2024-04-07 14:24:16 +02:00
21 changed files with 625 additions and 131 deletions

View File

@ -26,4 +26,4 @@ guidelines for adding new syntaxes:
[Name or description of the syntax/language here] [Name or description of the syntax/language here]
**Guideline Criteria:** **Guideline Criteria:**
[packagecontro.io link here] [packagecontrol.io link here]

View File

@ -6,6 +6,8 @@
- `bat --squeeze-blank`/`bat -s` will now squeeze consecutive empty lines, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) - `bat --squeeze-blank`/`bat -s` will now squeeze consecutive empty lines, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
- `bat --squeeze-limit` to set the maximum number of empty consecutive when using `--squeeze-blank`, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) - `bat --squeeze-limit` to set the maximum number of empty consecutive when using `--squeeze-blank`, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
- `PrettyPrinter::squeeze_empty_lines` to support line squeezing for bat as a library, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815) - `PrettyPrinter::squeeze_empty_lines` to support line squeezing for bat as a library, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
- Syntax highlighting for JavaScript files that start with `#!/usr/bin/env bun` #2913 (@sharunkumar)
- Add line length soft/hard limits to prevent out-of-memory events, see issue #636 and PR #2902 (@einfachIrgendwer0815)
## Bugfixes ## Bugfixes
@ -33,6 +35,8 @@
- Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon) - Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon)
- Apply clippy fixes #2864 (@cyqsimon) - Apply clippy fixes #2864 (@cyqsimon)
- Faster startup by offloading glob matcher building to a worker thread #2868 (@cyqsimon) - Faster startup by offloading glob matcher building to a worker thread #2868 (@cyqsimon)
- Display which theme is the default one in basic output (no colors), see #2937 (@sblondon)
- Display which theme is the default one in colored output, see #2838 (@sblondon)
## Syntaxes ## Syntaxes

24
Cargo.lock generated
View File

@ -647,9 +647,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.2" version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.1", "hashbrown 0.14.1",
@ -839,9 +839,9 @@ dependencies = [
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.6.1" version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" checksum = "7ac44c994af577c799b1b4bd80dc214701e349873ad894d6cdf96f4f7526e0b9"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1116,18 +1116,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.197" version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1156,9 +1156,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.7.0" version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
dependencies = [ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
@ -1167,9 +1167,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_with_macros" name = "serde_with_macros"
version = "3.7.0" version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",

View File

@ -66,7 +66,7 @@ regex = { version = "1.10.2", optional = true }
walkdir = { version = "2.4", optional = true } walkdir = { version = "2.4", optional = true }
bytesize = { version = "1.3.0" } bytesize = { version = "1.3.0" }
encoding_rs = "0.8.33" encoding_rs = "0.8.33"
os_str_bytes = { version = "~6.6", optional = true } os_str_bytes = { version = "~7.0", optional = true }
run_script = { version = "^0.10.1", optional = true} run_script = { version = "^0.10.1", optional = true}
[dependencies.git2] [dependencies.git2]
@ -102,13 +102,13 @@ nix = { version = "0.26.4", default-features = false, features = ["term"] }
[build-dependencies] [build-dependencies]
anyhow = "1.0.78" anyhow = "1.0.78"
indexmap = { version = "2.2.2", features = ["serde"] } indexmap = { version = "2.2.6", features = ["serde"] }
itertools = "0.12.1" itertools = "0.12.1"
once_cell = "1.18" once_cell = "1.18"
regex = "1.10.2" regex = "1.10.2"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_with = { version = "3.7.0", default-features = false, features = ["macros"] } serde_with = { version = "3.8.1", default-features = false, features = ["macros"] }
toml = { version = "0.8.9", features = ["preserve_order"] } toml = { version = "0.8.9", features = ["preserve_order"] }
walkdir = "2.4" walkdir = "2.4"

View File

@ -0,0 +1,14 @@
Submodule assets/syntaxes/01_Packages contains modified content
diff --git syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
index 05a4fed6..78a7bf55 100644
--- syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
+++ syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
@@ -5,7 +5,7 @@ name: JavaScript
file_extensions:
- js
- htc
-first_line_match: ^#!\s*/.*\b(node|js)\b
+first_line_match: ^#!\s*/.*\b(node|bun|js)\b
scope: source.js
variables:
bin_digit: '[01_]'

View File

@ -122,6 +122,17 @@ Options:
--squeeze-limit <squeeze-limit> --squeeze-limit <squeeze-limit>
Set the maximum number of consecutive empty lines to be printed. Set the maximum number of consecutive empty lines to be printed.
--disable-line-limits
Disables all line limits. Short for `--soft-line-limit 0 --hard-line-limit 0`.
--soft-line-limit <BYTES>
Line length (in bytes) at which the line will be ignored. Zero disables this limit.
Default: 64 kB
--hard-line-limit <BYTES>
Line length (in bytes) at which bat will abort. Zero disables this limit.
Default: 256 kB
--style <components> --style <components>
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..) Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
to display in addition to the file contents. The argument is a comma-separated list of to display in addition to the file contents. The argument is a comma-separated list of

View File

@ -45,6 +45,8 @@ Options:
Display all supported highlighting themes. Display all supported highlighting themes.
-s, --squeeze-blank -s, --squeeze-blank
Squeeze consecutive empty lines. Squeeze consecutive empty lines.
--disable-line-limits
Disables all line limits.
--style <components> --style <components>
Comma-separated list of style elements to display (*default*, auto, full, plain, changes, Comma-separated list of style elements to display (*default*, auto, full, plain, changes,
header, header-filename, header-filesize, grid, rule, numbers, snip). header, header-filename, header-filesize, grid, rule, numbers, snip).

View File

@ -471,7 +471,7 @@ mod tests {
let input = Input::ordinary_file(&file_path); let input = Input::ordinary_file(&file_path);
let dummy_stdin: &[u8] = &[]; let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin, None).unwrap(); let mut opened_input = input.open(dummy_stdin, None, None, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping) self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
} }
@ -481,7 +481,7 @@ mod tests {
let input = Input::from_reader(Box::new(BufReader::new(first_line.as_bytes()))) let input = Input::from_reader(Box::new(BufReader::new(first_line.as_bytes())))
.with_name(Some(&file_path)); .with_name(Some(&file_path));
let dummy_stdin: &[u8] = &[]; let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin, None).unwrap(); let mut opened_input = input.open(dummy_stdin, None, None, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping) self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
} }
@ -501,7 +501,7 @@ mod tests {
fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String { fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
let input = Input::stdin().with_name(Some(file_name)); let input = Input::stdin().with_name(Some(file_name));
let mut opened_input = input.open(content, None).unwrap(); let mut opened_input = input.open(content, None, None, None).unwrap();
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping) self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
} }
@ -698,7 +698,7 @@ mod tests {
let input = Input::ordinary_file(&file_path_symlink); let input = Input::ordinary_file(&file_path_symlink);
let dummy_stdin: &[u8] = &[]; let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin, None).unwrap(); let mut opened_input = input.open(dummy_stdin, None, None, None).unwrap();
assert_eq!( assert_eq!(
test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping), test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping),

View File

@ -304,6 +304,18 @@ impl App {
} else { } else {
None None
}, },
soft_line_limit: self
.matches
.get_one::<usize>("soft-line-limit")
.copied()
.filter(|l| l != &0)
.filter(|_| !self.matches.get_flag("disable-line-limits")),
hard_line_limit: self
.matches
.get_one::<usize>("hard-line-limit")
.copied()
.filter(|l| l != &0)
.filter(|_| !self.matches.get_flag("disable-line-limits")),
}) })
} }

View File

@ -402,6 +402,44 @@ pub fn build_app(interactive_output: bool) -> Command {
.long_help("Set the maximum number of consecutive empty lines to be printed.") .long_help("Set the maximum number of consecutive empty lines to be printed.")
.hide_short_help(true) .hide_short_help(true)
) )
.arg(
Arg::new("disable-line-limits")
.long("disable-line-limits")
.action(ArgAction::SetTrue)
.overrides_with_all(["soft-line-limit", "hard-line-limit"])
.help("Disables all line limits.")
.long_help("Disables all line limits. Short for `--soft-line-limit 0 --hard-line-limit 0`.")
)
.arg(
Arg::new("soft-line-limit")
.long("soft-line-limit")
.value_name("BYTES")
.value_parser(|s: &str| s.parse::<usize>())
.default_value("65536")
.overrides_with("disable-line-limits")
.long_help(
"Line length (in bytes) at which the line will be ignored. \
Zero disables this limit.\n\
Default: 64 kB",
)
.hide_short_help(true)
.hide_default_value(true)
)
.arg(
Arg::new("hard-line-limit")
.long("hard-line-limit")
.value_name("BYTES")
.value_parser(|s: &str| s.parse::<usize>())
.default_value("262144")
.overrides_with("disable-line-limits")
.long_help(
"Line length (in bytes) at which bat will abort. \
Zero disables this limit.\n\
Default: 256 kB"
)
.hide_short_help(true)
.hide_default_value(true)
)
.arg( .arg(
Arg::new("style") Arg::new("style")
.long("style") .long("style")

View File

@ -30,6 +30,7 @@ use directories::PROJECT_DIRS;
use globset::GlobMatcher; use globset::GlobMatcher;
use bat::{ use bat::{
assets::HighlightingAssets,
config::Config, config::Config,
controller::Controller, controller::Controller,
error::*, error::*,
@ -199,19 +200,31 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
if config.colored_output { let default_theme = HighlightingAssets::default_theme();
for theme in assets.themes() { for theme in assets.themes() {
let default_theme_info = if default_theme == theme {
" (default)"
} else {
""
};
if config.colored_output {
writeln!( writeln!(
stdout, stdout,
"Theme: {}\n", "Theme: {}{}\n",
Style::new().bold().paint(theme.to_string()) Style::new().bold().paint(theme.to_string()),
default_theme_info
)?; )?;
config.theme = theme.to_string(); config.theme = theme.to_string();
Controller::new(&config, &assets) Controller::new(&config, &assets)
.run(vec![theme_preview_file()], None) .run(vec![theme_preview_file()], None)
.ok(); .ok();
writeln!(stdout)?; writeln!(stdout)?;
} else {
writeln!(stdout, "{theme}{default_theme_info}")?;
} }
}
if config.colored_output {
writeln!( writeln!(
stdout, stdout,
"Further themes can be installed to '{}', \ "Further themes can be installed to '{}', \
@ -220,10 +233,6 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
https://github.com/sharkdp/bat#adding-new-themes", https://github.com/sharkdp/bat#adding-new-themes",
config_dir.join("themes").to_string_lossy() config_dir.join("themes").to_string_lossy()
)?; )?;
} else {
for theme in assets.themes() {
writeln!(stdout, "{theme}")?;
}
} }
Ok(()) Ok(())
@ -272,24 +281,25 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
.info(OperatingSystem::default()) .info(OperatingSystem::default())
.info(CommandLine::default()) .info(CommandLine::default())
.info(EnvironmentVariables::list(&[ .info(EnvironmentVariables::list(&[
"SHELL",
"PAGER",
"LESS",
"LANG",
"LC_ALL",
"BAT_PAGER",
"BAT_PAGING",
"BAT_CACHE_PATH", "BAT_CACHE_PATH",
"BAT_CONFIG_PATH", "BAT_CONFIG_PATH",
"BAT_OPTS", "BAT_OPTS",
"BAT_PAGER",
"BAT_PAGING",
"BAT_STYLE", "BAT_STYLE",
"BAT_TABS", "BAT_TABS",
"BAT_THEME", "BAT_THEME",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"COLORTERM", "COLORTERM",
"NO_COLOR", "LANG",
"LC_ALL",
"LESS",
"MANPAGER", "MANPAGER",
"NO_COLOR",
"PAGER",
"SHELL",
"TERM",
"XDG_CACHE_HOME",
"XDG_CONFIG_HOME",
])) ]))
.info(FileContent::new("System Config file", system_config_file())) .info(FileContent::new("System Config file", system_config_file()))
.info(FileContent::new("Config file", config_file())) .info(FileContent::new("Config file", config_file()))

View File

@ -100,6 +100,12 @@ pub struct Config<'a> {
/// The maximum number of consecutive empty lines to display /// The maximum number of consecutive empty lines to display
pub squeeze_lines: Option<usize>, pub squeeze_lines: Option<usize>,
/// Line length (in bytes) at which a line of input will be ignored
pub soft_line_limit: Option<usize>,
/// Line length (in bytes) at which an error will be thrown
pub hard_line_limit: Option<usize>,
} }
#[cfg(all(feature = "minimal-application", feature = "paging"))] #[cfg(all(feature = "minimal-application", feature = "paging"))]

View File

@ -5,7 +5,7 @@ use crate::config::{Config, VisibleLines};
#[cfg(feature = "git")] #[cfg(feature = "git")]
use crate::diff::{get_git_diff, LineChanges}; use crate::diff::{get_git_diff, LineChanges};
use crate::error::*; use crate::error::*;
use crate::input::{Input, InputReader, OpenedInput}; use crate::input::{Input, InputReader, OpenedInput, ReaderError};
#[cfg(feature = "lessopen")] #[cfg(feature = "lessopen")]
use crate::lessopen::LessOpenPreprocessor; use crate::lessopen::LessOpenPreprocessor;
#[cfg(feature = "git")] #[cfg(feature = "git")]
@ -142,7 +142,12 @@ impl<'b> Controller<'b> {
} }
#[cfg(not(feature = "lessopen"))] #[cfg(not(feature = "lessopen"))]
input.open(stdin, stdout_identifier)? input.open(
stdin,
stdout_identifier,
self.config.soft_line_limit,
self.config.hard_line_limit,
)?
}; };
#[cfg(feature = "git")] #[cfg(feature = "git")]
let line_changes = if self.config.visible_lines.diff_mode() let line_changes = if self.config.visible_lines.diff_mode()
@ -249,13 +254,30 @@ impl<'b> Controller<'b> {
let style_snip = self.config.style_components.snip(); let style_snip = self.config.style_components.snip();
while reader.read_line(&mut line_buffer)? { loop {
let mut soft_limit_hit = false;
let read_result = reader.read_line(&mut line_buffer);
match read_result {
Ok(res) => {
if !res {
break;
}
}
Err(err) => match err {
ReaderError::IoError(io_err) => return Err(io_err.into()),
ReaderError::SoftLimitHit => soft_limit_hit = true,
ReaderError::HardLimitHit => return Err(Error::LineTooLong(line_number)),
},
};
match line_ranges.check(line_number) { match line_ranges.check(line_number) {
RangeCheckResult::BeforeOrBetweenRanges => { RangeCheckResult::BeforeOrBetweenRanges => {
// Call the printer in case we need to call the syntax highlighter if !soft_limit_hit {
// for this line. However, set `out_of_range` to `true`. // Call the printer in case we need to call the syntax highlighter
printer.print_line(true, writer, line_number, &line_buffer)?; // for this line. However, set `out_of_range` to `true`.
mid_range = false; printer.print_line(true, writer, line_number, &line_buffer)?;
mid_range = false;
}
} }
RangeCheckResult::InRange => { RangeCheckResult::InRange => {
@ -268,8 +290,11 @@ impl<'b> Controller<'b> {
printer.print_snip(writer)?; printer.print_snip(writer)?;
} }
} }
if soft_limit_hit {
printer.print_line(false, writer, line_number, &line_buffer)?; printer.print_replaced_line(writer, line_number, "<line too long>")?;
} else {
printer.print_line(false, writer, line_number, &line_buffer)?;
}
} }
RangeCheckResult::AfterLastRange => { RangeCheckResult::AfterLastRange => {
break; break;

View File

@ -156,3 +156,33 @@ impl Decoration for GridBorderDecoration {
self.cached.width self.cached.width
} }
} }
pub(crate) struct PlaceholderDecoration {
cached: DecorationText,
}
impl PlaceholderDecoration {
pub(crate) fn new(length: usize) -> Self {
Self {
cached: DecorationText {
text: " ".repeat(length),
width: length,
},
}
}
}
impl Decoration for PlaceholderDecoration {
fn generate(
&self,
_line_number: usize,
_continuation: bool,
_printer: &InteractivePrinter,
) -> DecorationText {
self.cached.clone()
}
fn width(&self) -> usize {
self.cached.width
}
}

View File

@ -28,6 +28,8 @@ pub enum Error {
InvalidPagerValueBat, InvalidPagerValueBat,
#[error("{0}")] #[error("{0}")]
Msg(String), Msg(String),
#[error("Line {0} is too long")]
LineTooLong(usize),
#[cfg(feature = "lessopen")] #[cfg(feature = "lessopen")]
#[error(transparent)] #[error(transparent)]
VarError(#[from] ::std::env::VarError), VarError(#[from] ::std::env::VarError),

View File

@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
use clircle::{Clircle, Identifier}; use clircle::{Clircle, Identifier};
use content_inspector::{self, ContentType}; use content_inspector::{self, ContentType};
use once_cell::unsync::Lazy;
use crate::error::*; use crate::error::*;
@ -191,6 +192,8 @@ impl<'a> Input<'a> {
self, self,
stdin: R, stdin: R,
stdout_identifier: Option<&Identifier>, stdout_identifier: Option<&Identifier>,
soft_limit: Option<usize>,
hard_limit: Option<usize>,
) -> Result<OpenedInput<'a>> { ) -> Result<OpenedInput<'a>> {
let description = self.description().clone(); let description = self.description().clone();
match self.kind { match self.kind {
@ -207,7 +210,7 @@ impl<'a> Input<'a> {
kind: OpenedInputKind::StdIn, kind: OpenedInputKind::StdIn,
description, description,
metadata: self.metadata, metadata: self.metadata,
reader: InputReader::new(stdin), reader: InputReader::new(stdin, soft_limit, hard_limit),
}) })
} }
@ -236,48 +239,73 @@ impl<'a> Input<'a> {
file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened..."); file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened...");
} }
InputReader::new(BufReader::new(file)) InputReader::new(BufReader::new(file), soft_limit, hard_limit)
}, },
}), }),
InputKind::CustomReader(reader) => Ok(OpenedInput { InputKind::CustomReader(reader) => Ok(OpenedInput {
description, description,
kind: OpenedInputKind::CustomReader, kind: OpenedInputKind::CustomReader,
metadata: self.metadata, metadata: self.metadata,
reader: InputReader::new(BufReader::new(reader)), reader: InputReader::new(BufReader::new(reader), soft_limit, hard_limit),
}), }),
} }
} }
} }
pub(crate) struct InputReader<'a> { pub(crate) struct InputReader<'a> {
inner: Box<dyn BufRead + 'a>, inner: LimitBuf<'a>,
pub(crate) first_line: Vec<u8>, pub(crate) first_line: Vec<u8>,
pub(crate) content_type: Option<ContentType>, pub(crate) content_type: Option<ContentType>,
} }
impl<'a> InputReader<'a> { impl<'a> InputReader<'a> {
pub(crate) fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> { pub(crate) fn new<R: Read + 'a>(
let mut first_line = vec![]; reader: R,
reader.read_until(b'\n', &mut first_line).ok(); soft_limit: Option<usize>,
hard_limit: Option<usize>,
) -> InputReader<'a> {
let mut input_reader = InputReader {
inner: LimitBuf::new(
reader,
4096,
soft_limit.unwrap_or(usize::MAX),
hard_limit.unwrap_or(usize::MAX),
),
first_line: vec![],
content_type: None,
};
let content_type = if first_line.is_empty() { input_reader.read_first_line().ok();
let content_type = if input_reader.first_line.is_empty() {
None None
} else { } else {
Some(content_inspector::inspect(&first_line[..])) Some(content_inspector::inspect(&input_reader.first_line[..]))
}; };
if content_type == Some(ContentType::UTF_16LE) { if content_type == Some(ContentType::UTF_16LE) {
reader.read_until(0x00, &mut first_line).ok(); input_reader
.inner
.read_until(0x00, &mut input_reader.first_line)
.ok();
} }
InputReader { input_reader.content_type = content_type;
inner: Box::new(reader), input_reader
first_line,
content_type,
}
} }
pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> { fn read_first_line(&mut self) -> std::result::Result<bool, ReaderError> {
let mut first_line = vec![];
let res = self.read_line(&mut first_line);
self.first_line = first_line;
res
}
pub(crate) fn read_line(
&mut self,
buf: &mut Vec<u8>,
) -> std::result::Result<bool, ReaderError> {
if !self.first_line.is_empty() { if !self.first_line.is_empty() {
buf.append(&mut self.first_line); buf.append(&mut self.first_line);
return Ok(true); return Ok(true);
@ -293,10 +321,99 @@ impl<'a> InputReader<'a> {
} }
} }
struct LimitBuf<'a> {
reader: Box<dyn Read + 'a>,
inner: Box<[u8]>,
start: usize,
len: usize,
soft_limit: usize,
hard_limit: usize,
}
impl<'a> LimitBuf<'a> {
pub fn new<R: Read + 'a>(
reader: R,
buf_size: usize,
soft_limit: usize,
hard_limit: usize,
) -> Self {
Self {
reader: Box::new(reader),
inner: vec![0u8; buf_size].into_boxed_slice(),
start: 0,
len: 0,
soft_limit,
hard_limit,
}
}
pub fn read_until(
&mut self,
byte: u8,
buf: &mut Vec<u8>,
) -> std::result::Result<usize, ReaderError> {
let mut end_byte_reached = false;
let mut total_bytes = 0;
let mut soft_limit_hit = false;
let capacity = self.inner.len();
let mut drop_buf = Lazy::new(|| Vec::with_capacity(capacity));
while !end_byte_reached {
if self.len == 0 {
let bytes = self.reader.read(&mut self.inner)?;
self.len += bytes;
self.start = 0;
if bytes == 0 {
break;
}
}
let bytes = (&self.inner[self.start..self.start + self.len])
.read_until(byte, if soft_limit_hit { &mut drop_buf } else { buf })?;
end_byte_reached = self.inner[self.start + bytes - 1] == byte;
if soft_limit_hit {
drop_buf.clear();
}
self.len -= bytes;
self.start += bytes;
total_bytes += bytes;
if total_bytes > self.hard_limit {
return Err(ReaderError::HardLimitHit);
} else if total_bytes > self.soft_limit {
soft_limit_hit = true;
}
}
if soft_limit_hit {
Err(ReaderError::SoftLimitHit)
} else {
Ok(total_bytes)
}
}
}
#[derive(Debug)]
pub(crate) enum ReaderError {
SoftLimitHit,
HardLimitHit,
IoError(io::Error),
}
impl From<io::Error> for ReaderError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}
#[test] #[test]
fn basic() { fn basic() {
let content = b"#!/bin/bash\necho hello"; let content = b"#!/bin/bash\necho hello";
let mut reader = InputReader::new(&content[..]); let mut reader = InputReader::new(&content[..], None, None);
assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]); assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
@ -325,7 +442,7 @@ fn basic() {
#[test] #[test]
fn utf16le() { fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00"; let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
let mut reader = InputReader::new(&content[..]); let mut reader = InputReader::new(&content[..], None, None);
assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]); assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);

View File

@ -21,8 +21,10 @@ use unicode_width::UnicodeWidthChar;
use crate::assets::{HighlightingAssets, SyntaxReferenceInSet}; use crate::assets::{HighlightingAssets, SyntaxReferenceInSet};
use crate::config::Config; use crate::config::Config;
use crate::decorations;
#[cfg(feature = "git")] #[cfg(feature = "git")]
use crate::decorations::LineChangesDecoration; use crate::decorations::LineChangesDecoration;
use crate::decorations::PlaceholderDecoration;
use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration}; use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration};
#[cfg(feature = "git")] #[cfg(feature = "git")]
use crate::diff::LineChanges; use crate::diff::LineChanges;
@ -97,6 +99,13 @@ pub(crate) trait Printer {
line_number: usize, line_number: usize,
line_buffer: &[u8], line_buffer: &[u8],
) -> Result<()>; ) -> Result<()>;
fn print_replaced_line(
&mut self,
handle: &mut OutputHandle,
line_number: usize,
replace_text: &str,
) -> Result<()>;
} }
pub struct SimplePrinter<'a> { pub struct SimplePrinter<'a> {
@ -179,6 +188,15 @@ impl<'a> Printer for SimplePrinter<'a> {
} }
Ok(()) Ok(())
} }
fn print_replaced_line(
&mut self,
_handle: &mut OutputHandle,
_line_number: usize,
_replace_text: &str,
) -> Result<()> {
Ok(())
}
} }
struct HighlighterFromSet<'a> { struct HighlighterFromSet<'a> {
@ -240,6 +258,28 @@ impl<'a> InteractivePrinter<'a> {
} }
} }
let insert_placeholder = {
let git_feature_enabled = cfg!(feature = "git");
let changes_component;
#[cfg(feature = "git")]
{
changes_component = config.style_components.changes();
}
#[cfg(not(feature = "git"))]
{
changes_component = false;
}
let soft_limit_active = config.soft_line_limit.is_some();
let numbers_and_grid =
config.style_components.grid() && config.style_components.numbers();
(!git_feature_enabled || !changes_component) && numbers_and_grid && soft_limit_active
};
if insert_placeholder {
decorations.push(Box::new(PlaceholderDecoration::new(1)))
}
let mut panel_width: usize = let mut panel_width: usize =
decorations.len() + decorations.iter().fold(0, |a, x| a + x.width()); decorations.len() + decorations.iter().fold(0, |a, x| a + x.width());
@ -819,6 +859,54 @@ impl<'a> Printer for InteractivePrinter<'a> {
Ok(()) Ok(())
} }
fn print_replaced_line(
&mut self,
handle: &mut OutputHandle,
line_number: usize,
replace_text: &str,
) -> Result<()> {
if let Some(ContentType::BINARY) | None = self.content_type {
return Ok(());
}
if self.panel_width > 0 {
let mut width = 0;
if self.config.style_components.numbers() {
let line_numbers = decorations::LineNumberDecoration::new(&self.colors);
width += line_numbers.width();
write!(
handle,
"{} ",
line_numbers.generate(line_number, false, self).text
)?;
}
if self.config.style_components.grid() {
write!(handle, "{}", self.colors.error_indicator.paint("!"))?;
}
if width < self.panel_width {
write!(
handle,
"{}",
" ".repeat(self.panel_width.saturating_sub(width).saturating_sub(2))
)?;
}
if self.config.style_components.grid() {
let grid = decorations::GridBorderDecoration::new(&self.colors);
write!(handle, "{} ", grid.generate(line_number, false, self).text)?;
}
}
writeln!(
handle,
"{}",
self.colors.error_indicator.paint(replace_text)
)?;
Ok(())
}
} }
const DEFAULT_GUTTER_COLOR: u8 = 238; const DEFAULT_GUTTER_COLOR: u8 = 238;
@ -832,6 +920,7 @@ pub struct Colors {
pub git_removed: Style, pub git_removed: Style,
pub git_modified: Style, pub git_modified: Style,
pub line_number: Style, pub line_number: Style,
pub error_indicator: Style,
} }
impl Colors { impl Colors {
@ -861,6 +950,7 @@ impl Colors {
git_removed: Red.normal(), git_removed: Red.normal(),
git_modified: Yellow.normal(), git_modified: Yellow.normal(),
line_number: gutter_style, line_number: gutter_style,
error_indicator: Red.normal(),
} }
} }
} }

19
tests/examples/too-long-lines.txt vendored Normal file
View File

@ -0,0 +1,19 @@
a
bb
ccc
dddd
eeeee
ffffff
ggggggg
hhhhhhhh
iiiiiiiii
jjjjjjjjjj
kkkkkkkkk
llllllll
mmmmmmm
nnnnnn
ooooo
pppp
qqq
rr
s

View File

@ -272,6 +272,119 @@ fn squeeze_limit_line_numbers() {
.stdout(" 1 line 1\n 2 \n 3 \n 4 \n 5 line 5\n 6 \n 7 \n 8 \n 9 \n 10 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 29 \n 30 line 30\n"); .stdout(" 1 line 1\n 2 \n 3 \n 4 \n 5 line 5\n 6 \n 7 \n 8 \n 9 \n 10 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 29 \n 30 line 30\n");
} }
#[test]
fn list_themes_with_colors() {
#[cfg(target_os = "macos")]
let default_theme_chunk = "Monokai Extended Light\x1B[0m (default)";
#[cfg(not(target_os = "macos"))]
let default_theme_chunk = "Monokai Extended\x1B[0m (default)";
bat()
.arg("--color=always")
.arg("--list-themes")
.assert()
.success()
.stdout(predicate::str::contains("DarkNeon").normalize())
.stdout(predicate::str::contains(default_theme_chunk).normalize())
.stdout(predicate::str::contains("Output the square of a number.").normalize());
}
#[test]
fn list_themes_without_colors() {
#[cfg(target_os = "macos")]
let default_theme_chunk = "Monokai Extended Light (default)";
#[cfg(not(target_os = "macos"))]
let default_theme_chunk = "Monokai Extended (default)";
bat()
.arg("--color=never")
.arg("--list-themes")
.assert()
.success()
.stdout(predicate::str::contains("DarkNeon").normalize())
.stdout(predicate::str::contains(default_theme_chunk).normalize());
}
#[test]
fn soft_line_limit() {
bat()
.arg("too-long-lines.txt")
.arg("--soft-line-limit=10")
.arg("--decorations=always")
.arg("--terminal-width=80")
.assert()
.success()
.stdout(
"───────┬────────────────────────────────────────────────────────────────────────
File: too-long-lines.txt
1 a
2 bb
3 ccc
4 dddd
5 eeeee
6 ffffff
7 ggggggg
8 hhhhhhhh
9 iiiiiiiii
10 ! <line too long>
11 kkkkkkkkk
12 llllllll
13 mmmmmmm
14 nnnnnn
15 ooooo
16 pppp
17 qqq
18 rr
19 s
",
);
}
#[test]
fn soft_line_limit_style_plain() {
bat()
.arg("too-long-lines.txt")
.arg("--soft-line-limit=10")
.arg("--style=plain")
.assert()
.success()
.stdout(
"a
bb
ccc
dddd
eeeee
ffffff
ggggggg
hhhhhhhh
iiiiiiiii
kkkkkkkkk
llllllll
mmmmmmm
nnnnnn
ooooo
pppp
qqq
rr
s
",
);
}
#[test]
fn hard_line_limit() {
bat()
.arg("too-long-lines.txt")
.arg("--hard-line-limit=10")
.assert()
.failure()
.stderr("\u{1b}[31m[bat error]\u{1b}[0m: Line 10 is too long\n");
}
#[test] #[test]
#[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)] #[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)]
fn short_help() { fn short_help() {
@ -1230,11 +1343,11 @@ fn bom_stripped_when_no_color_and_not_loop_through() {
.success() .success()
.stdout( .stdout(
"\ "\
File: test_BOM.txt File: test_BOM.txt
1 hello world 1 hello world
", ",
); );
} }
@ -1482,15 +1595,16 @@ fn header_narrow_terminal() {
.success() .success()
.stdout( .stdout(
"\ "\
File: this-file-path-is File: this-file-path-
-really-long-and-would- is-really-long-and-wo
have-broken-the-layout- uld-have-broken-the-l
of-the-header.txt ayout-of-the-header.t
xt
1 The header is not broke
n 1 The header is not bro
ken
", ",
) )
.stderr(""); .stderr("");

View File

@ -1,27 +1,27 @@
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
│ File: sample.rs │ File: sample.rs
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
1 │ /// A rectangle. First line is changed to prevent a regression of #1869 1 │ /// A rectangle. First line is changed to prevent a regression of #1869
2 │ struct Rectangle { 2 │ struct Rectangle {
3 │ width: u32, 3 │ width: u32,
4 │ height: u32, 4 │ height: u32,
5 │ } 5 │ }
6 │ 6
7 │ fn main() { 7 │ fn main() {
8 │ let rect1 = Rectangle { width: 30, height: 50 }; 8 │ let rect1 = Rectangle { width: 30, height: 50 };
9 │ 9
10 │ println!( 10 │ println!(
11 │ "The perimeter of the rectangle is {} pixels.", 11 │ "The perimeter of the rectangle is {} pixels.",
12 │ perimeter(&rect1) 12 │ perimeter(&rect1)
13 │ ); 13 │ );
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#; 14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
15 │ } 15 │ }
16 │ 16
17 │ fn area(rectangle: &Rectangle) -> u32 { 17 │ fn area(rectangle: &Rectangle) -> u32 {
18 │ rectangle.width * rectangle.height 18 │ rectangle.width * rectangle.height
19 │ } 19 │ }
20 │ 20
21 │ fn perimeter(rectangle: &Rectangle) -> u32 { 21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
22 │ (rectangle.width + rectangle.height) * 2 22 │ (rectangle.width + rectangle.height) * 2
23 │ } 23 │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────

View File

@ -1,25 +1,25 @@
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────
1 │ /// A rectangle. First line is changed to prevent a regression of #1869 1 │ /// A rectangle. First line is changed to prevent a regression of #1869
2 │ struct Rectangle { 2 │ struct Rectangle {
3 │ width: u32, 3 │ width: u32,
4 │ height: u32, 4 │ height: u32,
5 │ } 5 │ }
6 │ 6
7 │ fn main() { 7 │ fn main() {
8 │ let rect1 = Rectangle { width: 30, height: 50 }; 8 │ let rect1 = Rectangle { width: 30, height: 50 };
9 │ 9
10 │ println!( 10 │ println!(
11 │ "The perimeter of the rectangle is {} pixels.", 11 │ "The perimeter of the rectangle is {} pixels.",
12 │ perimeter(&rect1) 12 │ perimeter(&rect1)
13 │ ); 13 │ );
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#; 14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
15 │ } 15 │ }
16 │ 16
17 │ fn area(rectangle: &Rectangle) -> u32 { 17 │ fn area(rectangle: &Rectangle) -> u32 {
18 │ rectangle.width * rectangle.height 18 │ rectangle.width * rectangle.height
19 │ } 19 │ }
20 │ 20
21 │ fn perimeter(rectangle: &Rectangle) -> u32 { 21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
22 │ (rectangle.width + rectangle.height) * 2 22 │ (rectangle.width + rectangle.height) * 2
23 │ } 23 │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────