Add new -M/--max-columns option.
This permits setting the maximum line width with respect to the number of bytes in a line. Omitted lines (whether part of a match, replacement or context) are replaced with a message stating that the line was elided. Fixes #129
This commit is contained in:
committed by
Andrew Gallant
parent
23aec58669
commit
d352b79294
@@ -3,12 +3,32 @@ use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use regex::bytes::Regex;
|
||||
use regex::bytes::{Regex, Replacer, Captures};
|
||||
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
|
||||
|
||||
use pathutil::strip_prefix;
|
||||
use ignore::types::FileTypeDef;
|
||||
|
||||
/// CountingReplacer implements the Replacer interface for Regex,
|
||||
/// and counts how often replacement is being performed.
|
||||
struct CountingReplacer<'r> {
|
||||
replace: &'r [u8],
|
||||
count: &'r mut usize,
|
||||
}
|
||||
|
||||
impl<'r> CountingReplacer<'r> {
|
||||
fn new(replace: &'r [u8], count: &'r mut usize) -> CountingReplacer<'r> {
|
||||
CountingReplacer { replace: replace, count: count }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Replacer for CountingReplacer<'r> {
|
||||
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
|
||||
*self.count += 1;
|
||||
caps.expand(self.replace, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Printer encapsulates all output logic for searching.
|
||||
///
|
||||
/// Note that we currently ignore all write errors. It's probably worthwhile
|
||||
@@ -46,6 +66,8 @@ pub struct Printer<W> {
|
||||
colors: ColorSpecs,
|
||||
/// The separator to use for file paths. If empty, this is ignored.
|
||||
path_separator: Option<u8>,
|
||||
/// Restrict lines to this many columns.
|
||||
max_columns: Option<usize>
|
||||
}
|
||||
|
||||
impl<W: WriteColor> Printer<W> {
|
||||
@@ -65,6 +87,7 @@ impl<W: WriteColor> Printer<W> {
|
||||
with_filename: false,
|
||||
colors: ColorSpecs::default(),
|
||||
path_separator: None,
|
||||
max_columns: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +167,12 @@ impl<W: WriteColor> Printer<W> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the max. number of columns used for printing matching lines.
|
||||
pub fn max_columns(mut self, max_columns: Option<usize>) -> Printer<W> {
|
||||
self.max_columns = max_columns;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if and only if something has been printed.
|
||||
pub fn has_printed(&self) -> bool {
|
||||
self.has_printed
|
||||
@@ -263,31 +292,57 @@ impl<W: WriteColor> Printer<W> {
|
||||
self.write(b":");
|
||||
}
|
||||
if self.replace.is_some() {
|
||||
let line = re.replace_all(
|
||||
&buf[start..end], &**self.replace.as_ref().unwrap());
|
||||
let mut count = 0;
|
||||
let line = {
|
||||
let replacer = CountingReplacer::new(
|
||||
self.replace.as_ref().unwrap(), &mut count);
|
||||
re.replace_all(&buf[start..end], replacer)
|
||||
};
|
||||
if self.max_columns.map_or(false, |m| line.len() > m) {
|
||||
let _ = self.wtr.set_color(self.colors.matched());
|
||||
let msg = format!(
|
||||
"[Omitted long line with {} replacements]", count);
|
||||
self.write(msg.as_bytes());
|
||||
let _ = self.wtr.reset();
|
||||
self.write_eol();
|
||||
return;
|
||||
}
|
||||
self.write(&line);
|
||||
if line.last() != Some(&self.eol) {
|
||||
self.write_eol();
|
||||
}
|
||||
} else {
|
||||
self.write_matched_line(re, &buf[start..end]);
|
||||
}
|
||||
if buf[start..end].last() != Some(&self.eol) {
|
||||
self.write_eol();
|
||||
// write_matched_line guarantees to write a newline.
|
||||
}
|
||||
}
|
||||
|
||||
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
||||
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
||||
self.write(buf);
|
||||
if self.max_columns.map_or(false, |m| buf.len() > m) {
|
||||
let count = re.find_iter(buf).count();
|
||||
let _ = self.wtr.set_color(self.colors.matched());
|
||||
let msg = format!("[Omitted long line with {} matches]", count);
|
||||
self.write(msg.as_bytes());
|
||||
let _ = self.wtr.reset();
|
||||
self.write_eol();
|
||||
return;
|
||||
}
|
||||
let mut last_written = 0;
|
||||
for m in re.find_iter(buf) {
|
||||
self.write(&buf[last_written..m.start()]);
|
||||
let _ = self.wtr.set_color(self.colors.matched());
|
||||
self.write(&buf[m.start()..m.end()]);
|
||||
let _ = self.wtr.reset();
|
||||
last_written = m.end();
|
||||
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
||||
self.write(buf);
|
||||
} else {
|
||||
let mut last_written = 0;
|
||||
for m in re.find_iter(buf) {
|
||||
self.write(&buf[last_written..m.start()]);
|
||||
let _ = self.wtr.set_color(self.colors.matched());
|
||||
self.write(&buf[m.start()..m.end()]);
|
||||
let _ = self.wtr.reset();
|
||||
last_written = m.end();
|
||||
}
|
||||
self.write(&buf[last_written..]);
|
||||
}
|
||||
if buf.last() != Some(&self.eol) {
|
||||
self.write_eol();
|
||||
}
|
||||
self.write(&buf[last_written..]);
|
||||
}
|
||||
|
||||
pub fn context<P: AsRef<Path>>(
|
||||
@@ -312,6 +367,11 @@ impl<W: WriteColor> Printer<W> {
|
||||
if let Some(line_number) = line_number {
|
||||
self.line_number(line_number, b'-');
|
||||
}
|
||||
if self.max_columns.map_or(false, |m| end - start > m) {
|
||||
self.write(format!("[Omitted long context line]").as_bytes());
|
||||
self.write_eol();
|
||||
return;
|
||||
}
|
||||
self.write(&buf[start..end]);
|
||||
if buf[start..end].last() != Some(&self.eol) {
|
||||
self.write_eol();
|
||||
|
||||
Reference in New Issue
Block a user