address review: report GREP search-budget exhaustion to the overlay warning

This commit is contained in:
Dylan M. Taylor committed 2026-11-03 19:42:30 +00:00
1 parent e8b96e7e02
commit 8ef09e8ed9
2 files changed
+25 -11

No files matched your search

+21 -10
View File
@@ -412,14 +412,24 @@ impl Grep {
/// Non-empty matches in `text`, at most `limit` of them: an empty match styles nothing, so it
/// doesn't count toward a GREP style's cap. Empty matches still cost a search each, so the
/// scan stops after `limit` × [`SEARCHES_PER_MATCH`] searches in all.
pub fn find_nonempty(&self, text: &str, limit: usize) -> Result<Vec<Range<usize>>, GrepError> {
self.re
.find_iter(text)
.take(limit.saturating_mul(SEARCHES_PER_MATCH))
.filter(|m| !m.as_ref().is_ok_and(|m| m.range().is_empty()))
.take(limit)
.map(|m| m.map(|m| m.range()).map_err(run_err))
.collect()
///
/// The flag is true when the search budget ran out with text left to scan, so the matches may
/// be incomplete (reaching `limit` is not flagged: the caller sees the full vector).
pub fn find_nonempty(&self, text: &str, limit: usize) -> Result<(Vec<Range<usize>>, bool), GrepError> {
let mut it = self.re.find_iter(text);
let mut out = Vec::new();
for _ in 0..limit.saturating_mul(SEARCHES_PER_MATCH) {
if out.len() >= limit {
return Ok((out, false));
}
let Some(m) = it.next() else { return Ok((out, false)) };
let r = m.map_err(run_err)?.range();
if !r.is_empty() {
out.push(r);
}
}
let more = out.len() < limit && it.next().is_some();
Ok((out, more))
}
/// Matches in `text` and their replacements, `template` expanded per match (`$1`…; see
@@ -533,9 +543,10 @@ mod tests {
// found within the budget and not past it.
let g = grep("a*");
let near = format!("{}a", "b".repeat(10 * SEARCHES_PER_MATCH - 2));
assert_eq!(g.find_nonempty(&near, 10).unwrap().len(), 1);
let (ms, exhausted) = g.find_nonempty(&near, 10).unwrap();
assert_eq!((ms.len(), ms[0].clone(), exhausted), (1, near.len() - 1..near.len(), false));
let far = format!("{}a", "b".repeat(10 * SEARCHES_PER_MATCH + 50));
assert!(g.find_nonempty(&far, 10).unwrap().is_empty(), "the scan stops at the search budget");
assert_eq!(g.find_nonempty(&far, 10).unwrap(), (vec![], true), "the scan stops at the search budget and says so");
}
#[test]
+4 -1
View File
@@ -107,7 +107,10 @@ pub(crate) fn overlays(text: &str, range: Range<usize>, nested: &[NestedStyle],
with_pattern(&g.pattern, |slot| {
let Some(re) = slot else { return };
match re.find_nonempty(text.get(range.start..end).unwrap_or(""), crate::grep::MAX_MATCHES_PER_PARAGRAPH) {
Ok(ms) => {
Ok((ms, exhausted)) => {
if exhausted {
log::warn!("GREP style {:?} ran out of search budget in one paragraph; the rest may not be styled", g.pattern);
}
if ms.len() == crate::grep::MAX_MATCHES_PER_PARAGRAPH {
log::warn!("GREP style {:?} matched more than {} times in one paragraph; the rest are not styled", g.pattern, ms.len());
}