diff --git a/crates/searcher/src/searcher/glue.rs b/crates/searcher/src/searcher/glue.rs index 5db57019..3a6527a5 100644 --- a/crates/searcher/src/searcher/glue.rs +++ b/crates/searcher/src/searcher/glue.rs @@ -144,6 +144,7 @@ pub(crate) struct MultiLine<'s, M, S> { core: Core<'s, M, S>, slice: &'s [u8], last_match: Option, + count: u64, } impl<'s, M: Matcher, S: Sink> MultiLine<'s, M, S> { @@ -160,6 +161,7 @@ impl<'s, M: Matcher, S: Sink> MultiLine<'s, M, S> { core: Core::new(searcher, matcher, write_to, true), slice, last_match: None, + count: 0, } } @@ -243,7 +245,7 @@ impl<'s, M: Matcher, S: Sink> MultiLine<'s, M, S> { // // See: https://github.com/BurntSushi/ripgrep/issues/1311 // And also the associated commit fixing #1311. - if last_match.end() >= line.start() { + if last_match.end() > line.start() { self.last_match = Some(last_match.with_end(line.end())); Ok(true) } else { @@ -325,10 +327,18 @@ impl<'s, M: Matcher, S: Sink> MultiLine<'s, M, S> { } fn find(&mut self) -> Result, S::Error> { + // if let Some(limit) = self.config.max_matches + // && self.count >= limit + // { + // return Ok(None); + // } match self.core.matcher().find(&self.slice[self.core.pos()..]) { Err(err) => Err(S::Error::error_message(err)), Ok(None) => Ok(None), - Ok(Some(m)) => Ok(Some(m.offset(self.core.pos()))), + Ok(Some(m)) => { + self.count += 1; + Ok(Some(m.offset(self.core.pos()))) + } } } diff --git a/crates/searcher/src/searcher/mod.rs b/crates/searcher/src/searcher/mod.rs index c7d3b1d1..ccd8945d 100644 --- a/crates/searcher/src/searcher/mod.rs +++ b/crates/searcher/src/searcher/mod.rs @@ -180,6 +180,8 @@ pub struct Config { /// Whether to stop searching when a non-matching line is found after a /// matching line. stop_on_nonmatch: bool, + /// The maximum number of matches this searcher should emit. + max_matches: Option, } impl Default for Config { @@ -198,6 +200,7 @@ impl Default for Config { encoding: None, bom_sniffing: true, stop_on_nonmatch: false, + max_matches: None, } } } @@ -564,6 +567,23 @@ impl SearcherBuilder { self.config.stop_on_nonmatch = stop_on_nonmatch; self } + + /// Sets the maximum number of matches that should be emitted by this + /// searcher. + /// + /// If multi line search is enabled and a match spans multiple lines, then + /// that match is counted exactly once for the purposes of enforcing this + /// limit, regardless of how many lines it spans. + /// + /// Note that `0` is a legal value. This will cause the searcher to + /// immediately quick without searching anything. + /// + /// By default, no limit is set. + #[inline] + pub fn max_matches(&mut self, limit: Option) -> &mut SearcherBuilder { + self.config.max_matches = limit; + self + } } /// A searcher executes searches over a haystack and writes results to a caller @@ -845,13 +865,27 @@ impl Searcher { self.config.multi_line } - /// Returns true if and only if this searcher is configured to stop when in + /// Returns true if and only if this searcher is configured to stop when it /// finds a non-matching line after a matching one. #[inline] pub fn stop_on_nonmatch(&self) -> bool { self.config.stop_on_nonmatch } + /// Returns the maximum number of matches emitted by this searcher, if + /// such a limit was set. + /// + /// If multi line search is enabled and a match spans multiple lines, then + /// that match is counted exactly once for the purposes of enforcing this + /// limit, regardless of how many lines it spans. + /// + /// Note that `0` is a legal value. This will cause the searcher to + /// immediately quick without searching anything. + #[inline] + pub fn max_matches(&self) -> Option { + self.config.max_matches + } + /// Returns true if and only if this searcher will choose a multi-line /// strategy given the provided matcher. ///