Add --vimgrep flag.

The --vimgrep flag forces a line to be printed for every match, with
line and column numbers.
This commit is contained in:
Andrew Gallant 2016-09-22 21:32:38 -04:00
parent 9981e7883a
commit dfebed6cbe
5 changed files with 82 additions and 5 deletions

View File

@ -237,6 +237,14 @@ Defaults to the number of logical CPUs (capped at 6).
Show the version number of ripgrep and exit. Show the version number of ripgrep and exit.
.RS .RS
.RE .RE
.TP
.B \-\-vimgrep
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 of the regex will be
printed more than once.)
.RS
.RE
.SH FILE TYPE MANAGEMENT OPTIONS .SH FILE TYPE MANAGEMENT OPTIONS
.TP .TP
.B \-\-type\-list .B \-\-type\-list

View File

@ -153,6 +153,11 @@ the raw speed of grep.
--version --version
: Show the version number of ripgrep and exit. : Show the version number of ripgrep and exit.
--vimgrep
: 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 of the regex will be printed more than once.)
# FILE TYPE MANAGEMENT OPTIONS # FILE TYPE MANAGEMENT OPTIONS
--type-list --type-list

View File

@ -153,6 +153,11 @@ Less common options:
--version --version
Show the version number of ripgrep and exit. Show the version number of ripgrep and exit.
--vimgrep
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 of the regex will be printed more than once.)
File type management options: File type management options:
--type-list --type-list
Show all supported file types and their associated globs. Show all supported file types and their associated globs.
@ -206,6 +211,7 @@ pub struct RawArgs {
flag_type_add: Vec<String>, flag_type_add: Vec<String>,
flag_type_clear: Vec<String>, flag_type_clear: Vec<String>,
flag_unrestricted: u32, flag_unrestricted: u32,
flag_vimgrep: bool,
flag_with_filename: bool, flag_with_filename: bool,
flag_word_regexp: bool, flag_word_regexp: bool,
} }
@ -231,6 +237,7 @@ pub struct Args {
ignore_case: bool, ignore_case: bool,
invert_match: bool, invert_match: bool,
line_number: bool, line_number: bool,
line_per_match: bool,
mmap: bool, mmap: bool,
no_ignore: bool, no_ignore: bool,
no_ignore_parent: bool, no_ignore_parent: bool,
@ -302,7 +309,9 @@ impl RawArgs {
self.flag_threads self.flag_threads
}; };
let color = let color =
if self.flag_color == "auto" { if self.flag_vimgrep {
false
} else if self.flag_color == "auto" {
atty::on_stdout() || self.flag_pretty atty::on_stdout() || self.flag_pretty
} else { } else {
self.flag_color == "always" self.flag_color == "always"
@ -344,6 +353,7 @@ impl RawArgs {
ignore_case: self.flag_ignore_case, ignore_case: self.flag_ignore_case,
invert_match: self.flag_invert_match, invert_match: self.flag_invert_match,
line_number: !self.flag_no_line_number && self.flag_line_number, line_number: !self.flag_no_line_number && self.flag_line_number,
line_per_match: self.flag_vimgrep,
mmap: mmap, mmap: mmap,
no_ignore: no_ignore, no_ignore: no_ignore,
no_ignore_parent: no_ignore_parent:
@ -367,6 +377,10 @@ impl RawArgs {
args.heading = true; args.heading = true;
} }
} }
if self.flag_vimgrep {
args.column = true;
args.line_number = true;
}
Ok(args) Ok(args)
} }
@ -490,6 +504,7 @@ impl Args {
.context_separator(self.context_separator.clone()) .context_separator(self.context_separator.clone())
.eol(self.eol) .eol(self.eol)
.heading(self.heading) .heading(self.heading)
.line_per_match(self.line_per_match)
.quiet(self.quiet) .quiet(self.quiet)
.with_filename(self.with_filename); .with_filename(self.with_filename);
if let Some(ref rep) = self.replace { if let Some(ref rep) = self.replace {

View File

@ -28,6 +28,8 @@ pub struct Printer<W> {
/// ///
/// N.B. If with_filename is false, then this setting has no effect. /// N.B. If with_filename is false, then this setting has no effect.
heading: bool, heading: bool,
/// Whether to show every match on its own line.
line_per_match: bool,
/// Whether to suppress all output. /// Whether to suppress all output.
quiet: bool, quiet: bool,
/// A string to use as a replacement of each match in a matching line. /// A string to use as a replacement of each match in a matching line.
@ -46,6 +48,7 @@ impl<W: Terminal + Send> Printer<W> {
context_separator: "--".to_string().into_bytes(), context_separator: "--".to_string().into_bytes(),
eol: b'\n', eol: b'\n',
heading: false, heading: false,
line_per_match: false,
quiet: false, quiet: false,
replace: None, replace: None,
with_filename: false, with_filename: false,
@ -79,6 +82,12 @@ impl<W: Terminal + Send> Printer<W> {
self self
} }
/// Whether to show every match on its own line.
pub fn line_per_match(mut self, yes: bool) -> Printer<W> {
self.line_per_match = yes;
self
}
/// When set, all output is suppressed. /// When set, all output is suppressed.
pub fn quiet(mut self, yes: bool) -> Printer<W> { pub fn quiet(mut self, yes: bool) -> Printer<W> {
self.quiet = yes; self.quiet = yes;
@ -165,6 +174,34 @@ impl<W: Terminal + Send> Printer<W> {
start: usize, start: usize,
end: usize, end: usize,
line_number: Option<u64>, line_number: Option<u64>,
) {
if !self.line_per_match {
let column =
if self.column {
Some(re.find(&buf[start..end])
.map(|(s, _)| s + 1).unwrap_or(0) as u64)
} else {
None
};
return self.write_match(
re, path, buf, start, end, line_number, column);
}
for (s, _) in re.find_iter(&buf[start..end]) {
let column = if self.column { Some(s as u64) } else { None };
self.write_match(
re, path.as_ref(), buf, start, end, line_number, column);
}
}
fn write_match<P: AsRef<Path>>(
&mut self,
re: &Regex,
path: P,
buf: &[u8],
start: usize,
end: usize,
line_number: Option<u64>,
column: Option<u64>,
) { ) {
if self.heading && self.with_filename && !self.has_printed { if self.heading && self.with_filename && !self.has_printed {
self.write_heading(path.as_ref()); self.write_heading(path.as_ref());
@ -175,8 +212,7 @@ impl<W: Terminal + Send> 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.column { if let Some(c) = column {
let c = re.find(&buf[start..end]).map(|(s, _)| s + 1).unwrap_or(0);
self.write(c.to_string().as_bytes()); self.write(c.to_string().as_bytes());
self.write(b":"); self.write(b":");
} }
@ -185,14 +221,14 @@ impl<W: Terminal + Send> Printer<W> {
&buf[start..end], &**self.replace.as_ref().unwrap()); &buf[start..end], &**self.replace.as_ref().unwrap());
self.write(&line); self.write(&line);
} else { } else {
self.write_match(re, &buf[start..end]); self.write_matched_line(re, &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();
} }
} }
pub fn write_match(&mut self, re: &Regex, buf: &[u8]) { fn write_matched_line(&mut self, re: &Regex, buf: &[u8]) {
if !self.wtr.supports_color() { if !self.wtr.supports_color() {
self.write(buf); self.write(buf);
return; return;

View File

@ -569,6 +569,19 @@ sherlock!(unrestricted3, "foo", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, "file:foo\x00bar\nfile:foo\x00baz\n"); assert_eq!(lines, "file:foo\x00bar\nfile:foo\x00baz\n");
}); });
sherlock!(vimgrep, "Sherlock|Watson", ".", |wd: WorkDir, mut cmd: Command| {
cmd.arg("--vimgrep");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:1:15:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:1:56:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:3:48:be, to a very large extent, the result of luck. Sherlock Holmes
sherlock:5:11:but Doctor Watson has to have it taken out for him and dusted,
";
assert_eq!(lines, expected);
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");