mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-04-08 16:53:58 +02:00
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:
parent
23aec58669
commit
d352b79294
@ -203,6 +203,10 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
-L, --follow
|
-L, --follow
|
||||||
: Follow symlinks.
|
: Follow symlinks.
|
||||||
|
|
||||||
|
-M, --max-columns *NUM*
|
||||||
|
: 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.
|
||||||
|
|
||||||
-m, --max-count *NUM*
|
-m, --max-count *NUM*
|
||||||
: Limit the number of matching lines per file searched to NUM.
|
: Limit the number of matching lines per file searched to NUM.
|
||||||
|
|
||||||
|
@ -169,6 +169,9 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
|
|||||||
.short("j").value_name("ARG").takes_value(true)
|
.short("j").value_name("ARG").takes_value(true)
|
||||||
.validator(validate_number))
|
.validator(validate_number))
|
||||||
.arg(flag("vimgrep"))
|
.arg(flag("vimgrep"))
|
||||||
|
.arg(flag("max-columns").short("M")
|
||||||
|
.value_name("NUM").takes_value(true)
|
||||||
|
.validator(validate_number))
|
||||||
.arg(flag("type-add")
|
.arg(flag("type-add")
|
||||||
.value_name("TYPE").takes_value(true)
|
.value_name("TYPE").takes_value(true)
|
||||||
.multiple(true).number_of_values(1))
|
.multiple(true).number_of_values(1))
|
||||||
@ -473,6 +476,11 @@ lazy_static! {
|
|||||||
"Show results with every match on its own line, including \
|
"Show results with every match on its own line, including \
|
||||||
line numbers and column numbers. With this option, a line with \
|
line numbers and column numbers. With this option, a line with \
|
||||||
more than one match will be printed more than once.");
|
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",
|
doc!(h, "type-add",
|
||||||
"Add a new glob for a file type.",
|
"Add a new glob for a file type.",
|
||||||
|
@ -56,6 +56,7 @@ pub struct Args {
|
|||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
line_per_match: bool,
|
line_per_match: bool,
|
||||||
|
max_columns: Option<usize>,
|
||||||
max_count: Option<u64>,
|
max_count: Option<u64>,
|
||||||
max_filesize: Option<u64>,
|
max_filesize: Option<u64>,
|
||||||
maxdepth: Option<usize>,
|
maxdepth: Option<usize>,
|
||||||
@ -156,7 +157,8 @@ impl Args {
|
|||||||
.line_per_match(self.line_per_match)
|
.line_per_match(self.line_per_match)
|
||||||
.null(self.null)
|
.null(self.null)
|
||||||
.path_separator(self.path_separator)
|
.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 {
|
if let Some(ref rep) = self.replace {
|
||||||
p = p.replace(rep.clone());
|
p = p.replace(rep.clone());
|
||||||
}
|
}
|
||||||
@ -348,6 +350,7 @@ impl<'a> ArgMatches<'a> {
|
|||||||
invert_match: self.is_present("invert-match"),
|
invert_match: self.is_present("invert-match"),
|
||||||
line_number: line_number,
|
line_number: line_number,
|
||||||
line_per_match: self.is_present("vimgrep"),
|
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_count: try!(self.usize_of("max-count")).map(|max| max as u64),
|
||||||
max_filesize: try!(self.max_filesize()),
|
max_filesize: try!(self.max_filesize()),
|
||||||
maxdepth: try!(self.usize_of("maxdepth")),
|
maxdepth: try!(self.usize_of("maxdepth")),
|
||||||
|
@ -3,12 +3,32 @@ use std::fmt;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use regex::bytes::Regex;
|
use regex::bytes::{Regex, Replacer, Captures};
|
||||||
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
|
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
|
||||||
|
|
||||||
use pathutil::strip_prefix;
|
use pathutil::strip_prefix;
|
||||||
use ignore::types::FileTypeDef;
|
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.
|
/// Printer encapsulates all output logic for searching.
|
||||||
///
|
///
|
||||||
/// Note that we currently ignore all write errors. It's probably worthwhile
|
/// Note that we currently ignore all write errors. It's probably worthwhile
|
||||||
@ -46,6 +66,8 @@ pub struct Printer<W> {
|
|||||||
colors: ColorSpecs,
|
colors: ColorSpecs,
|
||||||
/// The separator to use for file paths. If empty, this is ignored.
|
/// The separator to use for file paths. If empty, this is ignored.
|
||||||
path_separator: Option<u8>,
|
path_separator: Option<u8>,
|
||||||
|
/// Restrict lines to this many columns.
|
||||||
|
max_columns: Option<usize>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: WriteColor> Printer<W> {
|
impl<W: WriteColor> Printer<W> {
|
||||||
@ -65,6 +87,7 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
with_filename: false,
|
with_filename: false,
|
||||||
colors: ColorSpecs::default(),
|
colors: ColorSpecs::default(),
|
||||||
path_separator: None,
|
path_separator: None,
|
||||||
|
max_columns: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +167,12 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
self
|
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.
|
/// Returns true if and only if something has been printed.
|
||||||
pub fn has_printed(&self) -> bool {
|
pub fn has_printed(&self) -> bool {
|
||||||
self.has_printed
|
self.has_printed
|
||||||
@ -263,31 +292,57 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
self.write(b":");
|
self.write(b":");
|
||||||
}
|
}
|
||||||
if self.replace.is_some() {
|
if self.replace.is_some() {
|
||||||
let line = re.replace_all(
|
let mut count = 0;
|
||||||
&buf[start..end], &**self.replace.as_ref().unwrap());
|
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);
|
self.write(&line);
|
||||||
|
if line.last() != Some(&self.eol) {
|
||||||
|
self.write_eol();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.write_matched_line(re, &buf[start..end]);
|
self.write_matched_line(re, &buf[start..end]);
|
||||||
}
|
// write_matched_line guarantees to write a newline.
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
|
||||||
self.write_eol();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
|
||||||
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
if self.max_columns.map_or(false, |m| buf.len() > m) {
|
||||||
self.write(buf);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let mut last_written = 0;
|
if !self.wtr.supports_color() || self.colors.matched().is_none() {
|
||||||
for m in re.find_iter(buf) {
|
self.write(buf);
|
||||||
self.write(&buf[last_written..m.start()]);
|
} else {
|
||||||
let _ = self.wtr.set_color(self.colors.matched());
|
let mut last_written = 0;
|
||||||
self.write(&buf[m.start()..m.end()]);
|
for m in re.find_iter(buf) {
|
||||||
let _ = self.wtr.reset();
|
self.write(&buf[last_written..m.start()]);
|
||||||
last_written = m.end();
|
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>>(
|
pub fn context<P: AsRef<Path>>(
|
||||||
@ -312,6 +367,11 @@ impl<W: WriteColor> Printer<W> {
|
|||||||
if let Some(line_number) = line_number {
|
if let Some(line_number) = line_number {
|
||||||
self.line_number(line_number, b'-');
|
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]);
|
self.write(&buf[start..end]);
|
||||||
if buf[start..end].last() != Some(&self.eol) {
|
if buf[start..end].last() != Some(&self.eol) {
|
||||||
self.write_eol();
|
self.write_eol();
|
||||||
|
@ -1314,6 +1314,36 @@ clean!(feature_109_case_sensitive_part2, "test", ".",
|
|||||||
wd.assert_err(&mut cmd);
|
wd.assert_err(&mut cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_matches, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
cmd.arg("-M26");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:test\nfoo:[Omitted long line with 2 matches]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_context, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\nabcdefghijklmnopqrstuvwxyz");
|
||||||
|
cmd.arg("-M20").arg("-C1");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:test\nfoo-[Omitted long context line]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/129
|
||||||
|
clean!(feature_129_replace, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest abcdefghijklmnopqrstuvwxyz test");
|
||||||
|
cmd.arg("-M26").arg("-rfoo");
|
||||||
|
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
let expected = "foo:foo\nfoo:[Omitted long line with 2 replacements]\n";
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
});
|
||||||
|
|
||||||
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
wd.create("foo", "test\ntest");
|
wd.create("foo", "test\ntest");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user