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:
Ralf Jung
2017-02-02 15:29:50 +01:00
committed by Andrew Gallant
parent 23aec58669
commit d352b79294
5 changed files with 122 additions and 17 deletions

View File

@@ -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();