Skip to content

Commit

Permalink
limit amount of matches during replacement
Browse files Browse the repository at this point in the history
Skip matches beyond the original range when
replacing a buffer with captures.
  • Loading branch information
sonohgong committed Feb 24, 2022
1 parent a7005f3 commit 787df77
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 14 deletions.
30 changes: 30 additions & 0 deletions crates/matcher/src/lib.rs
Expand Up @@ -38,9 +38,11 @@ implementations.

#![deny(missing_docs)]

use std::cmp::min;
use std::fmt;
use std::io;
use std::ops;
use std::ops::Range;
use std::u64;

use crate::interpolate::interpolate;
Expand Down Expand Up @@ -942,6 +944,34 @@ pub trait Matcher {
Ok(())
}

/// Same as replace_with_captures_at, but limits the replacements to the
/// given range. Matches beyond the end of the range are skipped.
fn replace_with_captures_in_range<F>(
&self,
haystack: &[u8],
range: &Range<usize>,
caps: &mut Self::Captures,
dst: &mut Vec<u8>,
mut append: F,
) -> Result<(), Self::Error>
where
F: FnMut(&Self::Captures, &mut Vec<u8>) -> bool,
{
let mut last_match = range.start;
self.captures_iter_at(haystack, range.start, caps, |caps| {
let m = caps.get(0).unwrap();
if m.start >= range.end {
return false;
}
dst.extend(&haystack[last_match..m.start]);
last_match = m.end;
append(caps, dst)
})?;
let end = min(haystack.len(), range.end);
dst.extend(&haystack[last_match..end]);
Ok(())
}

/// Returns true if and only if the matcher matches the given haystack.
///
/// By default, this method is implemented by calling `shortest_match`.
Expand Down
18 changes: 4 additions & 14 deletions crates/printer/src/util.rs
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::cmp::max;
use std::fmt;
use std::io;
use std::path::Path;
Expand Down Expand Up @@ -63,13 +64,9 @@ impl<M: Matcher> Replacer<M> {
// See the giant comment in 'find_iter_at_in_context' below for why we
// do this dance.
let is_multi_line = searcher.multi_line_with_matcher(&matcher);
let mut extension_len = 0;
if is_multi_line {
if subject[range.end..].len() >= MAX_LOOK_AHEAD {
extension_len = MAX_LOOK_AHEAD;
subject = &subject[..range.end + MAX_LOOK_AHEAD];
} else {
extension_len = subject.len() - range.end;
}
} else {
// When searching a single line, we should remove the line
Expand All @@ -87,9 +84,9 @@ impl<M: Matcher> Replacer<M> {
matches.clear();

matcher
.replace_with_captures_at(
.replace_with_captures_in_range(
subject,
range.start,
&range,
caps,
dst,
|caps, dst| {
Expand All @@ -107,14 +104,7 @@ impl<M: Matcher> Replacer<M> {
)
.map_err(io::Error::error_message)?;

if is_multi_line {
// Remove the subject buffer beyond the range.end.
// NOTE: could this be a bug with the current replace functionality?
// As an example, running `rg -U '\.\nA' --replace 'BB' -C 2` in this repo produces
// spurious extra lines of output in the replacement, as the extra bytes where
// not removed. This seems to fix that, but it's not pretty and might be wrong.
dst.truncate(dst.len() - extension_len);
} else {
if !is_multi_line {
// Restore the line terminator.
dst.extend(searcher.line_terminator().as_bytes());
}
Expand Down

0 comments on commit 787df77

Please sign in to comment.