diff --git a/ci/script.sh b/ci/script.sh index 6f1c24ec..ee43f88e 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -16,6 +16,7 @@ disable_cross_doctests() { } run_test_suite() { + cargo clean --target $TARGET --verbose cargo build --target $TARGET --verbose cargo test --target $TARGET --verbose diff --git a/doc/rg.1 b/doc/rg.1 index 0df63968..1b437b94 100644 --- a/doc/rg.1 +++ b/doc/rg.1 @@ -35,6 +35,11 @@ Only show count of line matches for each file. .RS .RE .TP +.B \-l, \-\-files\-with\-matches +Only show path of each file with matches. +.RS +.RE +.TP .B \-\-color \f[I]WHEN\f[] Whether to use coloring in match. Valid values are never, always or auto. diff --git a/doc/rg.1.md b/doc/rg.1.md index 139582c3..ddc563dc 100644 --- a/doc/rg.1.md +++ b/doc/rg.1.md @@ -29,6 +29,9 @@ the raw speed of grep. -c, --count : Only show count of line matches for each file. +-l, --files-with-matches +: Only show path of each file with matches. + --color *WHEN* : Whether to use coloring in match. Valid values are never, always or auto. [default: auto] diff --git a/src/args.rs b/src/args.rs index 36c808da..72d4fdd8 100644 --- a/src/args.rs +++ b/src/args.rs @@ -47,6 +47,7 @@ rg recursively searches your current directory for a regex pattern. Common options: -a, --text Search binary files as if they were text. -c, --count Only show count of line matches for each file. + -l, --files-with-matches Only show path of each file with matches. --color WHEN Whether to use coloring in match. Valid values are never, always or auto. [default: auto] @@ -201,6 +202,7 @@ pub struct RawArgs { flag_context: usize, flag_context_separator: String, flag_count: bool, + flag_files_with_matches: bool, flag_debug: bool, flag_files: bool, flag_follow: bool, @@ -248,6 +250,7 @@ pub struct Args { column: bool, context_separator: Vec, count: bool, + files_with_matches: bool, eol: u8, files: bool, follow: bool, @@ -373,6 +376,7 @@ impl RawArgs { column: self.flag_column, context_separator: unescape(&self.flag_context_separator), count: self.flag_count, + files_with_matches: self.flag_files_with_matches, eol: eol, files: self.flag_files, follow: self.flag_follow, @@ -557,7 +561,7 @@ impl Args { /// to the writer given. pub fn out(&self) -> Out { let mut out = Out::new(self.color); - if self.heading && !self.count { + if self.heading && !self.count && !self.files_with_matches { out = out.file_separator(b"".to_vec()); } else if self.before_context > 0 || self.after_context > 0 { out = out.file_separator(self.context_separator.clone()); @@ -611,6 +615,7 @@ impl Args { .after_context(self.after_context) .before_context(self.before_context) .count(self.count) + .files_with_matches(self.files_with_matches) .eol(self.eol) .line_number(self.line_number) .invert_match(self.invert_match) @@ -629,6 +634,7 @@ impl Args { ) -> BufferSearcher<'a, W> { BufferSearcher::new(printer, grep, path, buf) .count(self.count) + .files_with_matches(self.files_with_matches) .eol(self.eol) .line_number(self.line_number) .invert_match(self.invert_match) diff --git a/src/search_buffer.rs b/src/search_buffer.rs index fb16bf0b..a3edbcb0 100644 --- a/src/search_buffer.rs +++ b/src/search_buffer.rs @@ -53,6 +53,14 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> { self } + /// If enabled, searching will print the path instead of each match. + /// + /// Disabled by default. + pub fn files_with_matches(mut self, yes: bool) -> Self { + self.opts.files_with_matches = yes; + self + } + /// Set the end-of-line byte used by this searcher. pub fn eol(mut self, eol: u8) -> Self { self.opts.eol = eol; @@ -96,6 +104,9 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> { self.print_match(m.start(), m.end()); } last_end = m.end(); + if self.opts.files_with_matches { + break; + } } if self.opts.invert_match { let upto = self.buf.len(); @@ -104,13 +115,16 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> { if self.opts.count && self.match_count > 0 { self.printer.path_count(self.path, self.match_count); } + if self.opts.files_with_matches && self.match_count > 0 { + self.printer.path(self.path); + } self.match_count } #[inline(always)] pub fn print_match(&mut self, start: usize, end: usize) { self.match_count += 1; - if self.opts.count { + if self.opts.skip_matches() { return; } self.count_lines(start); @@ -237,6 +251,14 @@ and exhibited clearly, with a label attached.\ assert_eq!(out, "/baz.rs:2\n"); } + #[test] + fn files_with_matches() { + let (count, out) = search( + "Sherlock", SHERLOCK, |s| s.files_with_matches(true)); + assert_eq!(1, count); + assert_eq!(out, "/baz.rs\n"); + } + #[test] fn invert_match() { let (count, out) = search( diff --git a/src/search_stream.rs b/src/search_stream.rs index fbae781c..7b661808 100644 --- a/src/search_stream.rs +++ b/src/search_stream.rs @@ -80,6 +80,7 @@ pub struct Options { pub after_context: usize, pub before_context: usize, pub count: bool, + pub files_with_matches: bool, pub eol: u8, pub invert_match: bool, pub line_number: bool, @@ -92,12 +93,22 @@ impl Default for Options { after_context: 0, before_context: 0, count: false, + files_with_matches: false, eol: b'\n', invert_match: false, line_number: false, text: false, } } + +} + +impl Options { + /// Both --count and --files-with-matches options imply that we should not + /// display matches at all. + pub fn skip_matches(&self) -> bool { + return self.count || self.files_with_matches; + } } impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { @@ -158,6 +169,14 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { self } + /// If enabled, searching will print the path instead of each match. + /// + /// Disabled by default. + pub fn files_with_matches(mut self, yes: bool) -> Self { + self.opts.files_with_matches = yes; + self + } + /// Set the end-of-line byte used by this searcher. pub fn eol(mut self, eol: u8) -> Self { self.opts.eol = eol; @@ -193,7 +212,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { self.line_count = if self.opts.line_number { Some(0) } else { None }; self.last_match = Match::default(); self.after_context_remaining = 0; - loop { + while !self.terminate() { let upto = self.inp.lastnl; self.print_after_context(upto); if !try!(self.fill()) { @@ -202,7 +221,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { if !self.opts.text && self.inp.is_binary { break; } - while self.inp.pos < self.inp.lastnl { + while !self.terminate() && self.inp.pos < self.inp.lastnl { let matched = self.grep.read_match( &mut self.last_match, &mut self.inp.buf[..self.inp.lastnl], @@ -234,12 +253,21 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { } } } - if self.opts.count && self.match_count > 0 { - self.printer.path_count(self.path, self.match_count); + if self.match_count > 0 { + if self.opts.count { + self.printer.path_count(self.path, self.match_count); + } else if self.opts.files_with_matches { + self.printer.path(self.path); + } } Ok(self.match_count) } + #[inline(always)] + fn terminate(&self) -> bool { + return self.opts.files_with_matches && self.match_count > 0; + } + #[inline(always)] fn fill(&mut self) -> Result { let mut keep = self.inp.lastnl; @@ -281,7 +309,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { #[inline(always)] fn print_before_context(&mut self, upto: usize) { - if self.opts.count || self.opts.before_context == 0 { + if self.opts.skip_matches() || self.opts.before_context == 0 { return; } let start = self.last_printed; @@ -304,7 +332,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { #[inline(always)] fn print_after_context(&mut self, upto: usize) { - if self.opts.count || self.after_context_remaining == 0 { + if self.opts.skip_matches() || self.after_context_remaining == 0 { return; } let start = self.last_printed; @@ -322,7 +350,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> { #[inline(always)] fn print_match(&mut self, start: usize, end: usize) { self.match_count += 1; - if self.opts.count { + if self.opts.skip_matches() { return; } self.print_separator(start); @@ -992,6 +1020,14 @@ fn main() { assert_eq!(out, "/baz.rs:2\n"); } + #[test] + fn files_with_matches() { + let (count, out) = search_smallcap( + "Sherlock", SHERLOCK, |s| s.files_with_matches(true)); + assert_eq!(1, count); + assert_eq!(out, "/baz.rs\n"); + } + #[test] fn invert_match() { let (count, out) = search_smallcap( diff --git a/tests/tests.rs b/tests/tests.rs index 72f5ab69..bcf62172 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -292,6 +292,20 @@ sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { assert_eq!(lines, "file.py:Sherlock\n"); }); +sherlock!(count, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { + cmd.arg("--count"); + let lines: String = wd.stdout(&mut cmd); + let expected = "sherlock:2\n"; + assert_eq!(lines, expected); +}); + +sherlock!(files_with_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { + cmd.arg("--files-with-matches"); + let lines: String = wd.stdout(&mut cmd); + let expected = "sherlock\n"; + assert_eq!(lines, expected); +}); + sherlock!(after_context, |wd: WorkDir, mut cmd: Command| { cmd.arg("-A").arg("1"); let lines: String = wd.stdout(&mut cmd);