pub mod engine;
pub mod pattern;
pub mod state;
pub use engine::{Direction, SearchMatch, Step, Wrapped, find_all, step, step_inclusive, word_at};
pub use pattern::{CaseMode, PatternError, SearchPattern};
pub use state::{Accepted, HISTORY_LIMIT, Prompt, SearchState};
#[cfg(test)]
mod integration {
use super::*;
const DOC: &str = "let foo = 1;\nlet bar = foo + 2;\nprintln!(\"{foo}\");\n";
#[test]
fn a_full_search_session_start_to_finish() {
let mut s = SearchState::new(CaseMode::Smart);
s.open(Direction::Forward, 0);
for c in "foo".chars() {
s.push(c);
}
assert!(s.preview(DOC).is_some());
assert!(s.pattern().is_none(), "preview must not commit");
assert_eq!(s.accept(DOC), Accepted::Committed);
assert_eq!(s.matches().len(), 3);
assert_eq!(s.highlights().len(), 3);
let a = s.repeat(0, false).unwrap();
let b = s.repeat(a.target.start, false).unwrap();
let c = s.repeat(b.target.start, false).unwrap();
assert!(a.target.start < b.target.start && b.target.start < c.target.start);
assert_eq!(c.wrapped, Wrapped::No, "still walking forward");
let d = s.repeat(c.target.start, false).unwrap();
assert_eq!(d.wrapped, Wrapped::AtBottom, "past the last match it wraps");
assert_eq!(d.target, a.target, "and lands back on the first");
s.clear_highlight();
assert!(s.highlights().is_empty());
assert!(s.repeat(0, false).is_some());
}
#[test]
fn smartcase_end_to_end() {
let mut s = SearchState::new(CaseMode::Smart);
s.open(Direction::Forward, 0);
for c in "LET".chars() {
s.push(c);
}
s.accept(DOC);
assert_eq!(s.matches().len(), 0, "uppercase pattern is case-sensitive");
s.open(Direction::Forward, 0);
for c in "let".chars() {
s.push(c);
}
s.accept(DOC);
assert_eq!(s.matches().len(), 2, "lowercase pattern ignores case");
}
#[test]
fn regex_patterns_work_end_to_end() {
let mut s = SearchState::new(CaseMode::Sensitive);
s.open(Direction::Forward, 0);
for c in r"let \w+ =".chars() {
s.push(c);
}
assert_eq!(s.accept(DOC), Accepted::Committed);
assert_eq!(s.matches().len(), 2);
}
#[test]
fn star_then_n_walks_the_identifier() {
let mut s = SearchState::new(CaseMode::Smart);
let first = s.search_word(DOC, 4, Direction::Forward).unwrap();
assert_eq!(s.pattern().unwrap().raw(), r"\bfoo\b");
assert_eq!(s.matches().len(), 3);
let next = s.repeat(first.target.start, false).unwrap();
assert!(next.target.start > first.target.start);
}
#[test]
fn search_survives_an_edit_via_refresh() {
let mut s = SearchState::new(CaseMode::Smart);
s.open(Direction::Forward, 0);
for c in "foo".chars() {
s.push(c);
}
s.accept(DOC);
assert_eq!(s.matches().len(), 3);
let edited = DOC.replace("let foo = 1;", "let qux = 1;");
s.refresh(&edited);
assert_eq!(s.matches().len(), 2, "one occurrence renamed away");
}
#[test]
fn unicode_document_reports_usable_char_offsets() {
let doc = "función foo\nla foo café\n";
let mut s = SearchState::new(CaseMode::Smart);
s.open(Direction::Forward, 0);
for c in "foo".chars() {
s.push(c);
}
s.accept(doc);
assert_eq!(s.matches().len(), 2);
for m in s.matches() {
let got: String = doc.chars().skip(m.start).take(m.len()).collect();
assert_eq!(got, "foo", "char offsets must slice back to the match");
}
}
}