ad_editor/dot/
find.rs

1//! Searching through the contents of a buffer for simple target patterns
2//!
3//! For more complex patterns, Regex should be used instead
4use crate::{
5    buffer::Buffer,
6    dot::{Cur, Dot, Range},
7    exec::IterBoundedChars,
8};
9
10/// A Find is able to locate its next occurance within an indexed character stream and return
11/// an optional pair of start/end indicies denoting the location of the next match.
12///
13/// For more complex patterns, Regex should be used instead
14pub trait Find {
15    /// The type of the Find being used when running backwards through a buffer
16    type Reversed: Find;
17
18    /// Construct the reversed version of this matcher for searching backwards through a buffer
19    fn reversed(&self) -> Self::Reversed;
20
21    /// Attempt to locate the start/end indices of the next match of this Matcher within a
22    /// character stream. Single character matches should be returned with start==end.
23    fn try_find<I>(&self, it: I) -> Option<(usize, usize)>
24    where
25        I: Iterator<Item = (usize, char)>;
26
27    fn expand(&self, dot: Dot, b: &Buffer) -> Dot
28    where
29        Self: Sized,
30    {
31        let Range {
32            mut start,
33            mut end,
34            start_active,
35        } = dot.as_range();
36
37        start = find_backward_start(self, start, b);
38        end = find_forward_end(self, end, b);
39
40        Dot::from(Range::from_cursors(start, end, start_active)).collapse_null_range()
41    }
42}
43
44pub fn find_forward<F: Find>(f: &F, cur: Cur, b: &Buffer) -> Option<Dot> {
45    find_between(f, cur.idx, b.txt.len_chars(), b)
46}
47
48pub fn find_forward_end<F: Find>(f: &F, cur: Cur, b: &Buffer) -> Cur {
49    find_forward(f, cur, b)
50        .unwrap_or_else(|| Cur::buffer_end(b).into())
51        .last_cur()
52}
53
54pub fn find_forward_wrapping<F: Find>(f: &F, b: &Buffer) -> Option<Dot> {
55    find_between(f, b.dot.last_cur().idx, b.txt.len_chars(), b)
56        .or_else(|| find_between(f, 0, b.dot.last_cur().idx, b))
57}
58
59pub fn find_backward<F: Find>(f: &F, cur: Cur, b: &Buffer) -> Option<Dot> {
60    rev_find_between(f, cur.idx, 0, b)
61}
62
63pub fn find_backward_start<F: Find>(f: &F, cur: Cur, b: &Buffer) -> Cur {
64    find_backward(f, cur, b).unwrap_or_default().first_cur()
65}
66
67// pub fn find_backward_wrapping<F: Find>(f: &F, b: &Buffer) -> Option<Dot> {
68//     rev_find_between(f, b.dot.first_cur().idx, 0, b)
69//         .or_else(|| rev_find_between(f, b.txt.len_chars(), b.dot.first_cur().idx, b))
70// }
71
72fn match_to_dot(m: Option<(usize, usize)>) -> Option<Dot> {
73    match m {
74        Some((start, end)) if start == end => Some(Cur { idx: start }.into()),
75        Some((start, end)) => Some(Dot::from_char_indices(start, end)),
76        None => None,
77    }
78}
79
80fn find_between<F: Find>(f: &F, from: usize, to: usize, b: &Buffer) -> Option<Dot> {
81    match_to_dot(f.try_find(b.iter_between(from, to)))
82}
83
84fn rev_find_between<F: Find>(f: &F, from: usize, to: usize, b: &Buffer) -> Option<Dot> {
85    match_to_dot(f.reversed().try_find(b.rev_iter_between(from, to)))
86}
87
88// Functions that check a single character
89impl Find for fn(char) -> bool {
90    type Reversed = fn(char) -> bool;
91
92    fn try_find<I>(&self, it: I) -> Option<(usize, usize)>
93    where
94        I: Iterator<Item = (usize, char)>,
95    {
96        for (i, ch) in it {
97            if (self)(ch) {
98                return Some((i, i));
99            }
100        }
101
102        None
103    }
104
105    fn reversed(&self) -> Self::Reversed {
106        *self
107    }
108}
109
110// Chars just need to locate themselves.
111impl Find for char {
112    type Reversed = char;
113
114    fn try_find<I>(&self, it: I) -> Option<(usize, usize)>
115    where
116        I: Iterator<Item = (usize, char)>,
117    {
118        for (i, ch) in it {
119            if ch == *self {
120                return Some((i, i));
121            }
122        }
123
124        None
125    }
126
127    fn reversed(&self) -> Self::Reversed {
128        *self
129    }
130}
131
132// Strings need to locate themselves character by character.
133impl Find for &str {
134    type Reversed = String;
135
136    fn try_find<I>(&self, it: I) -> Option<(usize, usize)>
137    where
138        I: Iterator<Item = (usize, char)>,
139    {
140        let chars: Vec<char> = self.chars().collect();
141        let last = chars.len().saturating_sub(1);
142        let mut cix = 0;
143        let mut start = 0;
144
145        for (i, ch) in it {
146            if ch != chars[cix] {
147                start = 0;
148                cix = 0;
149                continue;
150            }
151
152            if cix == 0 {
153                start = i;
154            }
155
156            if cix == last {
157                return Some((start, i));
158            }
159
160            cix += 1;
161        }
162
163        None
164    }
165
166    fn reversed(&self) -> Self::Reversed {
167        self.chars().rev().collect()
168    }
169}
170
171// Deferring to &str for the impl
172impl Find for String {
173    type Reversed = String;
174
175    fn try_find<I>(&self, it: I) -> Option<(usize, usize)>
176    where
177        I: Iterator<Item = (usize, char)>,
178    {
179        self.as_str().try_find(it)
180    }
181
182    fn reversed(&self) -> Self::Reversed {
183        self.chars().rev().collect()
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use simple_test_case::test_case;
191
192    #[test_case("this"; "first word")]
193    #[test_case("is"; "inner word two chars")]
194    #[test_case("a"; "inner word single char")]
195    #[test_case("find"; "inner word multiple chars")]
196    #[test_case("test"; "last word")]
197    #[test]
198    fn find_forward_str(s: &str) {
199        let b = Buffer::new_virtual(0, "test", "this is a find test");
200        let dot = find_forward_wrapping(&s, &b).expect("to find string");
201        let matched_text = dot.content(&b);
202
203        assert_eq!(matched_text, s);
204    }
205}