use regex::Regex;
pub struct Search {
pub pattern: Regex,
last_match: Option<usize>,
}
impl Search {
pub fn new(pattern: &str) -> Result<Self, regex::Error> {
Ok(Self {
pattern: Regex::new(pattern)?,
last_match: None,
})
}
pub fn find_forward(&mut self, lines: &[String], from: usize) -> Option<usize> {
let n = lines.len();
for i in from..n {
if self.pattern.is_match(&lines[i]) {
self.last_match = Some(i);
return Some(i);
}
}
for i in 0..from.min(n) {
if self.pattern.is_match(&lines[i]) {
self.last_match = Some(i);
return Some(i);
}
}
None
}
pub fn find_next(&mut self, lines: &[String]) -> Option<usize> {
let from = self.last_match.map(|i| i + 1).unwrap_or(0);
self.find_forward(lines, from)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lines(items: &[&str]) -> Vec<String> {
items.iter().map(|s| s.to_string()).collect()
}
#[test]
fn finds_first_occurrence_from_top() {
let mut s = Search::new("foo").unwrap();
let l = lines(&["bar", "foo bar", "baz"]);
assert_eq!(s.find_forward(&l, 0), Some(1));
}
#[test]
fn find_next_advances_past_last_match() {
let mut s = Search::new("x").unwrap();
let l = lines(&["x", "y", "x"]);
assert_eq!(s.find_forward(&l, 0), Some(0));
assert_eq!(s.find_next(&l), Some(2));
}
#[test]
fn find_forward_wraps_to_start() {
let mut s = Search::new("x").unwrap();
let l = lines(&["x", "y", "y"]);
assert_eq!(s.find_forward(&l, 1), Some(0));
}
#[test]
fn find_next_wraps() {
let mut s = Search::new("x").unwrap();
let l = lines(&["x", "y", "y"]);
s.find_forward(&l, 0); assert_eq!(s.find_next(&l), Some(0)); }
#[test]
fn returns_none_when_no_match_anywhere() {
let mut s = Search::new("nope").unwrap();
let l = lines(&["abc", "def"]);
assert_eq!(s.find_forward(&l, 0), None);
}
#[test]
fn invalid_regex_errors() {
assert!(Search::new("(unclosed").is_err());
}
}