mirror of https://github.com/sharkdp/bat.git
Compare commits
14 Commits
89b246ced1
...
5d22d486f6
Author | SHA1 | Date |
---|---|---|
einfachIrgendwer0815 | 5d22d486f6 | |
einfachIrgendwer0815 | d0bb0ba06e | |
einfachIrgendwer0815 | 1e52d10752 | |
einfachIrgendwer0815 | e34f073f7b | |
einfachIrgendwer0815 | 9594b0e4aa | |
einfachIrgendwer0815 | 8c1d7b18db | |
dependabot[bot] | b4e3a84e1a | |
dependabot[bot] | f7c39e8353 | |
dependabot[bot] | 37d9f0533c | |
dependabot[bot] | d560f2a515 | |
Stéphane Blondon | bb4d1cbd2e | |
Stéphane Blondon | 23ec433167 | |
Sharun | 9eaed3e3f0 | |
sblondon | d5bd4aa93f |
|
@ -6,6 +6,8 @@
|
|||
- `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)
|
||||
- `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
|
||||
|
||||
|
@ -33,6 +35,8 @@
|
|||
- Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon)
|
||||
- Apply clippy fixes #2864 (@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
|
||||
|
||||
|
|
|
@ -647,9 +647,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.2"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.1",
|
||||
|
@ -839,9 +839,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.6.1"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
checksum = "7ac44c994af577c799b1b4bd80dc214701e349873ad894d6cdf96f4f7526e0b9"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -1116,18 +1116,18 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
version = "1.0.199"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1156,9 +1156,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.7.0"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a"
|
||||
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
|
@ -1167,9 +1167,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.7.0"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655"
|
||||
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
|
|
|
@ -66,7 +66,7 @@ regex = { version = "1.10.2", optional = true }
|
|||
walkdir = { version = "2.4", optional = true }
|
||||
bytesize = { version = "1.3.0" }
|
||||
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}
|
||||
|
||||
[dependencies.git2]
|
||||
|
@ -102,13 +102,13 @@ nix = { version = "0.26.4", default-features = false, features = ["term"] }
|
|||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.78"
|
||||
indexmap = { version = "2.2.2", features = ["serde"] }
|
||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||
itertools = "0.12.1"
|
||||
once_cell = "1.18"
|
||||
regex = "1.10.2"
|
||||
serde = "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"] }
|
||||
walkdir = "2.4"
|
||||
|
||||
|
|
|
@ -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_]'
|
|
@ -122,6 +122,17 @@ Options:
|
|||
--squeeze-limit <squeeze-limit>
|
||||
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>
|
||||
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
|
||||
|
|
|
@ -45,6 +45,8 @@ Options:
|
|||
Display all supported highlighting themes.
|
||||
-s, --squeeze-blank
|
||||
Squeeze consecutive empty lines.
|
||||
--disable-line-limits
|
||||
Disables all line limits.
|
||||
--style <components>
|
||||
Comma-separated list of style elements to display (*default*, auto, full, plain, changes,
|
||||
header, header-filename, header-filesize, grid, rule, numbers, snip).
|
||||
|
|
|
@ -471,7 +471,7 @@ mod tests {
|
|||
|
||||
let input = Input::ordinary_file(&file_path);
|
||||
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)
|
||||
}
|
||||
|
@ -481,7 +481,7 @@ mod tests {
|
|||
let input = Input::from_reader(Box::new(BufReader::new(first_line.as_bytes())))
|
||||
.with_name(Some(&file_path));
|
||||
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)
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ mod tests {
|
|||
|
||||
fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
|
||||
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)
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ mod tests {
|
|||
|
||||
let input = Input::ordinary_file(&file_path_symlink);
|
||||
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!(
|
||||
test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping),
|
||||
|
|
|
@ -304,6 +304,18 @@ impl App {
|
|||
} else {
|
||||
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")),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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.")
|
||||
.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::new("style")
|
||||
.long("style")
|
||||
|
|
|
@ -30,6 +30,7 @@ use directories::PROJECT_DIRS;
|
|||
use globset::GlobMatcher;
|
||||
|
||||
use bat::{
|
||||
assets::HighlightingAssets,
|
||||
config::Config,
|
||||
controller::Controller,
|
||||
error::*,
|
||||
|
@ -199,19 +200,31 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
|
|||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
if config.colored_output {
|
||||
for theme in assets.themes() {
|
||||
let default_theme = HighlightingAssets::default_theme();
|
||||
for theme in assets.themes() {
|
||||
let default_theme_info = if default_theme == theme {
|
||||
" (default)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
if config.colored_output {
|
||||
writeln!(
|
||||
stdout,
|
||||
"Theme: {}\n",
|
||||
Style::new().bold().paint(theme.to_string())
|
||||
"Theme: {}{}\n",
|
||||
Style::new().bold().paint(theme.to_string()),
|
||||
default_theme_info
|
||||
)?;
|
||||
config.theme = theme.to_string();
|
||||
Controller::new(&config, &assets)
|
||||
.run(vec![theme_preview_file()], None)
|
||||
.ok();
|
||||
writeln!(stdout)?;
|
||||
} else {
|
||||
writeln!(stdout, "{theme}{default_theme_info}")?;
|
||||
}
|
||||
}
|
||||
|
||||
if config.colored_output {
|
||||
writeln!(
|
||||
stdout,
|
||||
"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",
|
||||
config_dir.join("themes").to_string_lossy()
|
||||
)?;
|
||||
} else {
|
||||
for theme in assets.themes() {
|
||||
writeln!(stdout, "{theme}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -100,6 +100,12 @@ pub struct Config<'a> {
|
|||
|
||||
/// The maximum number of consecutive empty lines to display
|
||||
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"))]
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::config::{Config, VisibleLines};
|
|||
#[cfg(feature = "git")]
|
||||
use crate::diff::{get_git_diff, LineChanges};
|
||||
use crate::error::*;
|
||||
use crate::input::{Input, InputReader, OpenedInput};
|
||||
use crate::input::{Input, InputReader, OpenedInput, ReaderError};
|
||||
#[cfg(feature = "lessopen")]
|
||||
use crate::lessopen::LessOpenPreprocessor;
|
||||
#[cfg(feature = "git")]
|
||||
|
@ -142,7 +142,12 @@ impl<'b> Controller<'b> {
|
|||
}
|
||||
|
||||
#[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")]
|
||||
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();
|
||||
|
||||
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) {
|
||||
RangeCheckResult::BeforeOrBetweenRanges => {
|
||||
// Call the printer in case we need to call the syntax highlighter
|
||||
// for this line. However, set `out_of_range` to `true`.
|
||||
printer.print_line(true, writer, line_number, &line_buffer)?;
|
||||
mid_range = false;
|
||||
if !soft_limit_hit {
|
||||
// Call the printer in case we need to call the syntax highlighter
|
||||
// for this line. However, set `out_of_range` to `true`.
|
||||
printer.print_line(true, writer, line_number, &line_buffer)?;
|
||||
mid_range = false;
|
||||
}
|
||||
}
|
||||
|
||||
RangeCheckResult::InRange => {
|
||||
|
@ -268,8 +290,11 @@ impl<'b> Controller<'b> {
|
|||
printer.print_snip(writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
printer.print_line(false, writer, line_number, &line_buffer)?;
|
||||
if soft_limit_hit {
|
||||
printer.print_replaced_line(writer, line_number, "<line too long>")?;
|
||||
} else {
|
||||
printer.print_line(false, writer, line_number, &line_buffer)?;
|
||||
}
|
||||
}
|
||||
RangeCheckResult::AfterLastRange => {
|
||||
break;
|
||||
|
|
|
@ -156,3 +156,33 @@ impl Decoration for GridBorderDecoration {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ pub enum Error {
|
|||
InvalidPagerValueBat,
|
||||
#[error("{0}")]
|
||||
Msg(String),
|
||||
#[error("Line {0} is too long")]
|
||||
LineTooLong(usize),
|
||||
#[cfg(feature = "lessopen")]
|
||||
#[error(transparent)]
|
||||
VarError(#[from] ::std::env::VarError),
|
||||
|
|
153
src/input.rs
153
src/input.rs
|
@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use clircle::{Clircle, Identifier};
|
||||
use content_inspector::{self, ContentType};
|
||||
use once_cell::unsync::Lazy;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
|
@ -191,6 +192,8 @@ impl<'a> Input<'a> {
|
|||
self,
|
||||
stdin: R,
|
||||
stdout_identifier: Option<&Identifier>,
|
||||
soft_limit: Option<usize>,
|
||||
hard_limit: Option<usize>,
|
||||
) -> Result<OpenedInput<'a>> {
|
||||
let description = self.description().clone();
|
||||
match self.kind {
|
||||
|
@ -207,7 +210,7 @@ impl<'a> Input<'a> {
|
|||
kind: OpenedInputKind::StdIn,
|
||||
description,
|
||||
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...");
|
||||
}
|
||||
|
||||
InputReader::new(BufReader::new(file))
|
||||
InputReader::new(BufReader::new(file), soft_limit, hard_limit)
|
||||
},
|
||||
}),
|
||||
InputKind::CustomReader(reader) => Ok(OpenedInput {
|
||||
description,
|
||||
kind: OpenedInputKind::CustomReader,
|
||||
metadata: self.metadata,
|
||||
reader: InputReader::new(BufReader::new(reader)),
|
||||
reader: InputReader::new(BufReader::new(reader), soft_limit, hard_limit),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InputReader<'a> {
|
||||
inner: Box<dyn BufRead + 'a>,
|
||||
inner: LimitBuf<'a>,
|
||||
pub(crate) first_line: Vec<u8>,
|
||||
pub(crate) content_type: Option<ContentType>,
|
||||
}
|
||||
|
||||
impl<'a> InputReader<'a> {
|
||||
pub(crate) fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
|
||||
let mut first_line = vec![];
|
||||
reader.read_until(b'\n', &mut first_line).ok();
|
||||
pub(crate) fn new<R: Read + 'a>(
|
||||
reader: R,
|
||||
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
|
||||
} else {
|
||||
Some(content_inspector::inspect(&first_line[..]))
|
||||
Some(content_inspector::inspect(&input_reader.first_line[..]))
|
||||
};
|
||||
|
||||
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 {
|
||||
inner: Box::new(reader),
|
||||
first_line,
|
||||
content_type,
|
||||
}
|
||||
input_reader.content_type = content_type;
|
||||
input_reader
|
||||
}
|
||||
|
||||
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() {
|
||||
buf.append(&mut self.first_line);
|
||||
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]
|
||||
fn basic() {
|
||||
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[..]);
|
||||
|
||||
|
@ -325,7 +442,7 @@ fn basic() {
|
|||
#[test]
|
||||
fn utf16le() {
|
||||
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[..]);
|
||||
|
||||
|
|
|
@ -21,8 +21,10 @@ use unicode_width::UnicodeWidthChar;
|
|||
|
||||
use crate::assets::{HighlightingAssets, SyntaxReferenceInSet};
|
||||
use crate::config::Config;
|
||||
use crate::decorations;
|
||||
#[cfg(feature = "git")]
|
||||
use crate::decorations::LineChangesDecoration;
|
||||
use crate::decorations::PlaceholderDecoration;
|
||||
use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration};
|
||||
#[cfg(feature = "git")]
|
||||
use crate::diff::LineChanges;
|
||||
|
@ -97,6 +99,13 @@ pub(crate) trait Printer {
|
|||
line_number: usize,
|
||||
line_buffer: &[u8],
|
||||
) -> Result<()>;
|
||||
|
||||
fn print_replaced_line(
|
||||
&mut self,
|
||||
handle: &mut OutputHandle,
|
||||
line_number: usize,
|
||||
replace_text: &str,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct SimplePrinter<'a> {
|
||||
|
@ -179,6 +188,15 @@ impl<'a> Printer for SimplePrinter<'a> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_replaced_line(
|
||||
&mut self,
|
||||
_handle: &mut OutputHandle,
|
||||
_line_number: usize,
|
||||
_replace_text: &str,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
decorations.len() + decorations.iter().fold(0, |a, x| a + x.width());
|
||||
|
||||
|
@ -819,6 +859,54 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||
|
||||
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;
|
||||
|
@ -832,6 +920,7 @@ pub struct Colors {
|
|||
pub git_removed: Style,
|
||||
pub git_modified: Style,
|
||||
pub line_number: Style,
|
||||
pub error_indicator: Style,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
|
@ -861,6 +950,7 @@ impl Colors {
|
|||
git_removed: Red.normal(),
|
||||
git_modified: Yellow.normal(),
|
||||
line_number: gutter_style,
|
||||
error_indicator: Red.normal(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
a
|
||||
bb
|
||||
ccc
|
||||
dddd
|
||||
eeeee
|
||||
ffffff
|
||||
ggggggg
|
||||
hhhhhhhh
|
||||
iiiiiiiii
|
||||
jjjjjjjjjj
|
||||
kkkkkkkkk
|
||||
llllllll
|
||||
mmmmmmm
|
||||
nnnnnn
|
||||
ooooo
|
||||
pppp
|
||||
qqq
|
||||
rr
|
||||
s
|
|
@ -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");
|
||||
}
|
||||
|
||||
#[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]
|
||||
#[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)]
|
||||
fn short_help() {
|
||||
|
@ -1230,11 +1343,11 @@ fn bom_stripped_when_no_color_and_not_loop_through() {
|
|||
.success()
|
||||
.stdout(
|
||||
"\
|
||||
─────┬──────────────────────────────────────────────────────────────────────────
|
||||
│ File: test_BOM.txt
|
||||
─────┼──────────────────────────────────────────────────────────────────────────
|
||||
1 │ hello world
|
||||
─────┴──────────────────────────────────────────────────────────────────────────
|
||||
───────┬────────────────────────────────────────────────────────────────────────
|
||||
│ File: test_BOM.txt
|
||||
───────┼────────────────────────────────────────────────────────────────────────
|
||||
1 │ hello world
|
||||
───────┴────────────────────────────────────────────────────────────────────────
|
||||
",
|
||||
);
|
||||
}
|
||||
|
@ -1482,15 +1595,16 @@ fn header_narrow_terminal() {
|
|||
.success()
|
||||
.stdout(
|
||||
"\
|
||||
─────┬────────────────────────
|
||||
│ File: this-file-path-is
|
||||
│ -really-long-and-would-
|
||||
│ have-broken-the-layout-
|
||||
│ of-the-header.txt
|
||||
─────┼────────────────────────
|
||||
1 │ The header is not broke
|
||||
│ n
|
||||
─────┴────────────────────────
|
||||
───────┬──────────────────────
|
||||
│ File: this-file-path-
|
||||
│ is-really-long-and-wo
|
||||
│ uld-have-broken-the-l
|
||||
│ ayout-of-the-header.t
|
||||
│ xt
|
||||
───────┼──────────────────────
|
||||
1 │ The header is not bro
|
||||
│ ken
|
||||
───────┴──────────────────────
|
||||
",
|
||||
)
|
||||
.stderr("");
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
─────┬──────────────────────────────────────────────────────────────────────────
|
||||
│ File: sample.rs
|
||||
─────┼──────────────────────────────────────────────────────────────────────────
|
||||
1 │ /// A rectangle. First line is changed to prevent a regression of #1869
|
||||
2 │ struct Rectangle {
|
||||
3 │ width: u32,
|
||||
4 │ height: u32,
|
||||
5 │ }
|
||||
6 │
|
||||
7 │ fn main() {
|
||||
8 │ let rect1 = Rectangle { width: 30, height: 50 };
|
||||
9 │
|
||||
10 │ println!(
|
||||
11 │ "The perimeter of the rectangle is {} pixels.",
|
||||
12 │ perimeter(&rect1)
|
||||
13 │ );
|
||||
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
|
||||
15 │ }
|
||||
16 │
|
||||
17 │ fn area(rectangle: &Rectangle) -> u32 {
|
||||
18 │ rectangle.width * rectangle.height
|
||||
19 │ }
|
||||
20 │
|
||||
21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
|
||||
22 │ (rectangle.width + rectangle.height) * 2
|
||||
23 │ }
|
||||
─────┴──────────────────────────────────────────────────────────────────────────
|
||||
───────┬────────────────────────────────────────────────────────────────────────
|
||||
│ File: sample.rs
|
||||
───────┼────────────────────────────────────────────────────────────────────────
|
||||
1 │ /// A rectangle. First line is changed to prevent a regression of #1869
|
||||
2 │ struct Rectangle {
|
||||
3 │ width: u32,
|
||||
4 │ height: u32,
|
||||
5 │ }
|
||||
6 │
|
||||
7 │ fn main() {
|
||||
8 │ let rect1 = Rectangle { width: 30, height: 50 };
|
||||
9 │
|
||||
10 │ println!(
|
||||
11 │ "The perimeter of the rectangle is {} pixels.",
|
||||
12 │ perimeter(&rect1)
|
||||
13 │ );
|
||||
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
|
||||
15 │ }
|
||||
16 │
|
||||
17 │ fn area(rectangle: &Rectangle) -> u32 {
|
||||
18 │ rectangle.width * rectangle.height
|
||||
19 │ }
|
||||
20 │
|
||||
21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
|
||||
22 │ (rectangle.width + rectangle.height) * 2
|
||||
23 │ }
|
||||
───────┴────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
─────┬──────────────────────────────────────────────────────────────────────────
|
||||
1 │ /// A rectangle. First line is changed to prevent a regression of #1869
|
||||
2 │ struct Rectangle {
|
||||
3 │ width: u32,
|
||||
4 │ height: u32,
|
||||
5 │ }
|
||||
6 │
|
||||
7 │ fn main() {
|
||||
8 │ let rect1 = Rectangle { width: 30, height: 50 };
|
||||
9 │
|
||||
10 │ println!(
|
||||
11 │ "The perimeter of the rectangle is {} pixels.",
|
||||
12 │ perimeter(&rect1)
|
||||
13 │ );
|
||||
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
|
||||
15 │ }
|
||||
16 │
|
||||
17 │ fn area(rectangle: &Rectangle) -> u32 {
|
||||
18 │ rectangle.width * rectangle.height
|
||||
19 │ }
|
||||
20 │
|
||||
21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
|
||||
22 │ (rectangle.width + rectangle.height) * 2
|
||||
23 │ }
|
||||
─────┴──────────────────────────────────────────────────────────────────────────
|
||||
───────┬────────────────────────────────────────────────────────────────────────
|
||||
1 │ /// A rectangle. First line is changed to prevent a regression of #1869
|
||||
2 │ struct Rectangle {
|
||||
3 │ width: u32,
|
||||
4 │ height: u32,
|
||||
5 │ }
|
||||
6 │
|
||||
7 │ fn main() {
|
||||
8 │ let rect1 = Rectangle { width: 30, height: 50 };
|
||||
9 │
|
||||
10 │ println!(
|
||||
11 │ "The perimeter of the rectangle is {} pixels.",
|
||||
12 │ perimeter(&rect1)
|
||||
13 │ );
|
||||
14 │ println!(r#"This line contains invalid utf8: "<22><><EFBFBD><EFBFBD><EFBFBD>"#;
|
||||
15 │ }
|
||||
16 │
|
||||
17 │ fn area(rectangle: &Rectangle) -> u32 {
|
||||
18 │ rectangle.width * rectangle.height
|
||||
19 │ }
|
||||
20 │
|
||||
21 │ fn perimeter(rectangle: &Rectangle) -> u32 {
|
||||
22 │ (rectangle.width + rectangle.height) * 2
|
||||
23 │ }
|
||||
───────┴────────────────────────────────────────────────────────────────────────
|
||||
|
|
Loading…
Reference in New Issue