use crate::message::MessageId;
use crate::wrap::LineSource;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Match {
pub message: MessageId,
pub source: LineSource,
pub start: usize,
pub end: usize,
}
impl Match {
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start && pos < self.end
}
}
#[derive(Debug, Default, Clone)]
pub struct SearchState {
needle: String,
case_sensitive: bool,
matches: Vec<Match>,
current: Option<usize>,
}
impl SearchState {
pub fn new() -> SearchState {
SearchState::default()
}
pub fn needle(&self) -> &str {
&self.needle
}
pub fn case_sensitive(&self) -> bool {
self.case_sensitive
}
pub fn is_active(&self) -> bool {
!self.needle.is_empty()
}
pub fn matches(&self) -> &[Match] {
&self.matches
}
pub fn len(&self) -> usize {
self.matches.len()
}
pub fn is_empty(&self) -> bool {
self.matches.is_empty()
}
pub fn ordinal(&self) -> Option<usize> {
self.current.map(|i| i + 1)
}
pub fn current(&self) -> Option<Match> {
self.current.and_then(|i| self.matches.get(i)).copied()
}
pub fn current_index(&self) -> Option<usize> {
self.current
}
pub fn is_current(&self, m: &Match) -> bool {
self.current() == Some(*m)
}
pub fn clear(&mut self) {
self.needle.clear();
self.matches.clear();
self.current = None;
}
pub fn set_results(&mut self, needle: &str, case_sensitive: bool, matches: Vec<Match>) {
self.needle = needle.to_string();
self.case_sensitive = case_sensitive;
self.matches = matches;
self.current = None;
}
pub fn seek_from(&mut self, order: impl Fn(MessageId) -> Option<usize>, from_row: usize) {
if self.matches.is_empty() {
self.current = None;
return;
}
let at = self
.matches
.iter()
.position(|m| order(m.message).is_some_and(|r| r >= from_row));
self.current = Some(at.unwrap_or(self.matches.len() - 1));
}
pub fn step(&mut self, dir: i32) -> Option<Match> {
if self.matches.is_empty() {
self.current = None;
return None;
}
let n = self.matches.len();
self.current = Some(match (self.current, dir >= 0) {
(None, true) => 0,
(None, false) => n - 1,
(Some(i), true) => (i + 1) % n,
(Some(i), false) => (i + n - 1) % n,
});
self.current()
}
}
pub fn find_all(haystack: &str, needle: &str, case_sensitive: bool) -> Vec<(usize, usize)> {
let mut out = Vec::new();
if needle.is_empty() || haystack.is_empty() {
return out;
}
if case_sensitive {
let mut base = 0usize;
while let Some(rel) = haystack[base..].find(needle) {
let start = base + rel;
let end = start + needle.len();
out.push((start, end));
base = end;
}
return out;
}
let mut start = 0usize;
while start < haystack.len() {
if !haystack.is_char_boundary(start) {
start += 1;
continue;
}
match match_at(&haystack[start..], needle) {
Some(len) => {
out.push((start, start + len));
start += len.max(1);
}
None => {
start += haystack[start..]
.chars()
.next()
.map(char::len_utf8)
.unwrap_or(1);
}
}
}
out
}
fn match_at(hay: &str, needle: &str) -> Option<usize> {
let mut h = hay.chars();
let mut n = needle.chars();
let mut used = 0usize;
loop {
let Some(nc) = n.next() else {
return Some(used);
};
let hc = h.next()?;
if !eq_fold(hc, nc) {
return None;
}
used += hc.len_utf8();
}
}
fn eq_fold(a: char, b: char) -> bool {
if a == b {
return true;
}
let mut al = a.to_lowercase();
let mut bl = b.to_lowercase();
match (al.next(), bl.next()) {
(Some(x), Some(y)) => x == y && al.next().is_none() && bl.next().is_none(),
_ => false,
}
}