Skip to content

Commit 5dd7c24

Browse files
committed
fix(rg): cap passthru replacement matches
1 parent 5d6cb63 commit 5dd7c24

1 file changed

Lines changed: 76 additions & 11 deletions

File tree

  • crates/bashkit/src/builtins/rg

crates/bashkit/src/builtins/rg/mod.rs

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

440452
impl 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+
44784504
fn 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, &regex, &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

Comments
 (0)