Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ impl<'a> OutputWriter<'a> {
}
}

/// Returns true if line numbers will be padded with whitespace (`-T`),
/// which means that you should call `set_line_number_width`.
/// Returns true if line numbers or byte offsets will be padded with
/// whitespace (`-T`), which means that you should call `set_line_number_width`.
pub fn wants_padded_line_numbers(&self) -> bool {
self.config.initial_tab && self.config.line_number
self.config.initial_tab && (self.config.line_number || self.config.byte_offset)
}

/// Set the minimum field width for line numbers (used with `-T`).
/// Set the minimum field width for line numbers and byte offsets (used with `-T`).
///
/// It's sort of wrong for this to be a setter, because it's technically
/// per-session state in searcher.rs, but it's convenient and it's cheap.
Expand Down Expand Up @@ -143,9 +143,10 @@ impl<'a> OutputWriter<'a> {
}

if self.config.byte_offset {
let width = self.line_number_width;
self.write_colored_fmt(
self.config.color_config.byte_offset,
format_args!("{}", byte_offset),
format_args!("{:>width$}", byte_offset, width = width),
)?;
self.write_separator(sep_char)?;
}
Expand Down
17 changes: 13 additions & 4 deletions src/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ pub struct Searcher<'a> {
session_last_match_end: u64,
}

/// The `-T` field width for an input of `size` bytes: the digits of its size.
fn padded_width(size: u64) -> usize {
size.max(1).ilog10() as usize + 1
}

impl<'a> Searcher<'a> {
pub fn new(config: &'a Config<'a>, matcher: Matcher<'a>, writer: OutputWriter<'a>) -> Self {
Self {
Expand Down Expand Up @@ -107,9 +112,14 @@ impl<'a> Searcher<'a> {

let path = Path::new(&self.config.label);

// Stdin is not seekable; use fixed width.
// Like a named file, a regular file on stdin sizes the field from its
// length; anything else (a pipe, a terminal) gets a fixed width.
if self.writer.wants_padded_line_numbers() {
self.writer.set_line_number_width(19);
let width = match stdin.metadata() {
Ok(m) if m.is_file() => padded_width(m.len()),
_ => 19,
};
self.writer.set_line_number_width(width);
}

// Where standard input was before we started buffering ahead of it.
Expand Down Expand Up @@ -208,8 +218,7 @@ impl<'a> Searcher<'a> {
let result = File::open(path).and_then(|mut file| {
if self.writer.wants_padded_line_numbers() {
let file_size = file.metadata().map_or(0, |m| m.len());
self.writer
.set_line_number_width(file_size.max(1).ilog10() as usize + 1);
self.writer.set_line_number_width(padded_width(file_size));
}
self.session_run(lb, path, &mut file)
});
Expand Down
31 changes: 31 additions & 0 deletions tests/test_grep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,37 @@ fn line_number_and_byte_offset_prefixes() {
.stdout_contains("1:\tx\n");
}

#[test]
fn initial_tab_pads_to_the_input_size() {
// GNU grep pads line numbers and byte offsets under -T to the number of
// digits of the input's size; 100 bytes gives three columns.
let (scene, mut c) = ucmd();
let text = format!("{}x\n", "a".repeat(98));
scene.fixtures.write("f", &text);
let expected = format!(" 1: 0:\t{text}");
c.args(&["-T", "-n", "-b", "x", "f"])
.succeeds()
.stdout_only(&expected);

// A regular file on stdin is sized the same way.
scene
.cmd(env!("CARGO_BIN_EXE_grep"))
.args(&["-T", "-n", "-b", "x"])
.set_stdin(std::fs::File::open(scene.fixtures.plus("f")).unwrap())
.succeeds()
.stdout_only(&expected);

// A pipe has no size, so it gets the fixed width. On Windows the
// metadata of a pipe looks like a regular file's.
#[cfg(unix)]
scene
.cmd(env!("CARGO_BIN_EXE_grep"))
.args(&["-bT", "x"])
.pipe_in("x\n")
.succeeds()
.stdout_only(format!("{:>19}:\tx\n", 0));
}

#[test]
fn null_filename_separator() {
let (scene, mut c) = ucmd();
Expand Down
Loading