pub fn focus_block(text: &str, re: ®ex::Regex, ctx: usize) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let mut keep = vec![false; lines.len()];
let mut any = false;
for (i, l) in lines.iter().enumerate() {
if re.is_match(l) {
any = true;
let lo = i.saturating_sub(ctx);
let hi = (i + ctx).min(lines.len().saturating_sub(1));
keep[lo..=hi].iter_mut().for_each(|k| *k = true);
}
}
if !any {
return None;
}
let mut out = String::new();
let mut prev: Option<usize> = None;
for (i, &k) in keep.iter().enumerate() {
if !k {
continue;
}
if let Some(p) = prev
&& i > p + 1
{
out.push_str("--\n");
}
out.push_str(&format!("{}: {}\n", i + 1, lines[i]));
prev = Some(i);
}
Some(out.trim_end().to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pattern::compile;
#[test]
fn focus_keeps_matching_windows_with_context() {
let re = compile("ERROR").unwrap();
let log = "a\nb\nERROR x\nd\ne\nf\nERROR y\nh\n";
let block = focus_block(log, &re, 1).unwrap();
assert_eq!(block, "2: b\n3: ERROR x\n4: d\n--\n6: f\n7: ERROR y\n8: h");
}
#[test]
fn focus_none_when_no_match() {
let re = compile("ERROR").unwrap();
assert!(focus_block("nothing relevant\n", &re, 2).is_none());
}
}