This commit is contained in:
einfachIrgendwer0815 2024-05-05 08:18:43 +00:00 committed by GitHub
commit 5d22d486f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 529 additions and 97 deletions

View File

@ -7,6 +7,7 @@
- `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) - 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

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

@ -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

@ -307,6 +307,84 @@ fn list_themes_without_colors() {
.stdout(predicate::str::contains(default_theme_chunk).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() {
@ -1265,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
", ",
); );
} }
@ -1517,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 │ }
─────────────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────