1use crate::{
5 buffer::Buffer,
6 dot::{Cur, Dot, Range},
7 exec::IterBoundedChars,
8};
9
10pub trait Find {
15 type Reversed: Find;
17
18 fn reversed(&self) -> Self::Reversed;
20
21 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
67fn 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
88impl 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
110impl 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
132impl 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
171impl 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}