diff options
author | Ralf Jung <post@ralfj.de> | 2017-02-02 15:29:50 +0100 |
---|---|---|
committer | Andrew Gallant <jamslam@gmail.com> | 2017-03-12 21:21:28 -0400 |
commit | d352b792944af6dc5d818b1ed3119f0914dfce38 (patch) | |
tree | 8f199062da76cdee8c7e5558ea9f8795e2160200 /src | |
parent | 23aec586696afb5d3cda1081d63f9641caad1045 (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 8 | ||||
-rw-r--r-- | src/args.rs | 5 | ||||
-rw-r--r-- | src/printer.rs | 90 |
3 files changed, 87 insertions, 16 deletions
@@ -169,6 +169,9 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static> .short("j").value_name("ARG").takes_value(true) .validator(validate_number)) .arg(flag("vimgrep")) + .arg(flag("max-columns").short("M") + .value_name("NUM").takes_value(true) + .validator(validate_number)) .arg(flag("type-add") .value_name("TYPE").takes_value(true) .multiple(true).number_of_values(1)) @@ -473,6 +476,11 @@ lazy_static! { "Show results with every match on its own line, including \ line numbers and column numbers. With this option, a line with \ more than one match will be printed more than once."); + doc!(h, "max-columns", + "Don't print lines longer than this limit in bytes.", + "Don't print lines longer than this limit in bytes. Longer lines \ + are omitted, and only the number of matches in that line is \ + printed."); doc!(h, "type-add", "Add a new glob for a file type.", diff --git a/src/args.rs b/src/args.rs index cc48b7ad..148ae8b7 100644 --- a/src/args.rs +++ b/src/args.rs @@ -56,6 +56,7 @@ pub struct Args { invert_match: bool, line_number: bool, line_per_match: bool, + max_columns: Option<usize>, max_count: Option<u64>, max_filesize: Option<u64>, maxdepth: Option<usize>, @@ -156,7 +157,8 @@ impl Args { .line_per_match(self.line_per_match) .null(self.null) .path_separator(self.path_separator) - .with_filename(self.with_filename); + .with_filename(self.with_filename) + .max_columns(self.max_columns); if let Some(ref rep) = self.replace { p = p.replace(rep.clone()); } @@ -348,6 +350,7 @@ impl<'a> ArgMatches<'a> { invert_match: self.is_present("invert-match"), line_number: line_number, line_per_match: self.is_present("vimgrep"), + max_columns: try!(self.usize_of("max-columns")), max_count: try!(self.usize_of("max-count")).map(|max| max as u64), max_filesize: try!(self.max_filesize()), maxdepth: try!(self.usize_of("maxdepth")), diff --git a/src/printer.rs b/src/printer.rs index 8c04dd1a..809e0d75 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -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.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; + } if !self.wtr.supports_color() || self.colors.matched().is_none() { self.write(buf); - return; + } 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..]); } - 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 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(); |