@@ -416,25 +416,37 @@ fn rg_replacement_cap_marker() -> String {
416416 )
417417}
418418
419- fn replacement_output_exceeds_cap (
419+ fn replacement_output_projection (
420420 haystack_len: usize,
421421 replacement: &str,
422422 match_count: usize,
423423 include_unmatched_text: bool,
424- ) -> bool {
424+ ) -> usize {
425425 let capture_ref_count = replacement.bytes().filter(|&byte| byte == b'$').count();
426426 let per_match = replacement
427427 .len()
428428 .saturating_add(capture_ref_count.saturating_mul(haystack_len));
429- let projected =
430- match_count
431- .saturating_mul(per_match)
432- .saturating_add(if include_unmatched_text {
433- haystack_len
434- } else {
435- 0
436- });
437- projected > RG_MAX_REPLACEMENT_OUTPUT_BYTES
429+ match_count
430+ .saturating_mul(per_match)
431+ .saturating_add(if include_unmatched_text {
432+ haystack_len
433+ } else {
434+ 0
435+ })
436+ }
437+
438+ fn replacement_output_exceeds_cap(
439+ haystack_len: usize,
440+ replacement: &str,
441+ match_count: usize,
442+ include_unmatched_text: bool,
443+ ) -> bool {
444+ replacement_output_projection(
445+ haystack_len,
446+ replacement,
447+ match_count,
448+ include_unmatched_text,
449+ ) > RG_MAX_REPLACEMENT_OUTPUT_BYTES
438450}
439451
440452impl RgMatcher {
@@ -4475,6 +4487,20 @@ fn format_rg_match_text(text: &str, regex: &RgMatcher, opts: &RgOptions) -> Stri
44754487 }
44764488}
44774489
4490+ fn rg_multiline_replacement_matches_exceed_cap(
4491+ matches: &[RgMultilineMatch<'_>],
4492+ replacement: &str,
4493+ ) -> bool {
4494+ matches
4495+ .iter()
4496+ .map(|mat| replacement_output_projection(mat.text.len(), replacement, 1, true))
4497+ .try_fold(0usize, |total, projected| {
4498+ let total = total.saturating_add(projected);
4499+ (total <= RG_MAX_REPLACEMENT_OUTPUT_BYTES).then_some(total)
4500+ })
4501+ .is_none()
4502+ }
4503+
44784504fn rg_multiline_match_segments(
44794505 mat: RgMultilineMatch<'_>,
44804506 regex: &RgMatcher,
@@ -6040,6 +6066,17 @@ impl Builtin for Rg {
60406066 while line_idx < lines.len() {
60416067 if opts.only_matching {
60426068 if let Some(line_matches) = matches_by_start_line.get(&line_idx) {
6069+ if let Some(replacement) = &opts.replacement
6070+ && rg_multiline_replacement_matches_exceed_cap(
6071+ line_matches,
6072+ replacement,
6073+ )
6074+ {
6075+ output.push_str(&rg_replacement_cap_marker());
6076+ output.push(record_terminator);
6077+ line_idx += 1;
6078+ continue;
6079+ }
60436080 for &mat in line_matches {
60446081 let segments = rg_multiline_match_segments(mat, ®ex, &opts);
60456082 write_rg_multiline_match_segments(
@@ -6496,6 +6533,17 @@ impl Builtin for Rg {
64966533 for (line_idx, line) in lines.iter().enumerate() {
64976534 let matched = match_line_set.contains(&line_idx);
64986535 if opts.only_matching && !opts.invert_match && matched {
6536+ if let Some(replacement) = &opts.replacement
6537+ && regex.replacement_matches_exceed_cap(
6538+ line.match_text,
6539+ replacement.as_str(),
6540+ )
6541+ {
6542+ output.push_str(&rg_replacement_cap_marker());
6543+ output.push(record_terminator);
6544+ truncate_rg_output(&mut output, output_limit);
6545+ continue;
6546+ }
64996547 regex.for_each_match(line.match_text, |mat| {
65006548 write_rg_prefix(
65016549 &mut output,
@@ -14136,6 +14184,23 @@ mod tests {
1413614184 "a",
1413714185 "/big.txt",
1413814186 ],
14187+ vec![
14188+ "--passthru",
14189+ "--only-matching",
14190+ "--replace",
14191+ replacement.as_str(),
14192+ "a",
14193+ "/big.txt",
14194+ ],
14195+ vec![
14196+ "--multiline",
14197+ "--passthru",
14198+ "--only-matching",
14199+ "--replace",
14200+ replacement.as_str(),
14201+ "a",
14202+ "/big.txt",
14203+ ],
1413914204 ] {
1414014205 let result = run_rg(&args, None, files).await;
1414114206
0 commit comments