1use super::cursor::CursorPos;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum FindReplaceMode {
8 #[default]
10 Find,
11 Replace,
13}
14
15#[derive(Clone, Debug, Default)]
17pub struct FindOptions {
18 pub case_sensitive: bool,
20 pub whole_word: bool,
22 pub use_regex: bool,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct FindMatch {
29 pub start: CursorPos,
31 pub end: CursorPos,
33}
34
35impl FindMatch {
36 pub fn new(start: CursorPos, end: CursorPos) -> Self {
38 Self { start, end }
39 }
40}
41
42#[derive(Clone, Debug, Default)]
44pub struct FindReplaceState {
45 pub query: String,
47 pub replace_with: String,
49 pub options: FindOptions,
51 pub matches: Vec<FindMatch>,
53 pub current_match: Option<usize>,
55 pub mode: FindReplaceMode,
57 pub query_focused: bool,
59}
60
61impl FindReplaceState {
62 pub fn new(mode: FindReplaceMode) -> Self {
64 Self {
65 mode,
66 query_focused: true,
67 ..Default::default()
68 }
69 }
70
71 pub fn match_count(&self) -> usize {
73 self.matches.len()
74 }
75
76 pub fn current_match_display(&self) -> usize {
78 self.current_match.map(|i| i + 1).unwrap_or(0)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
91 fn test_find_replace_mode_default() {
92 assert_eq!(FindReplaceMode::default(), FindReplaceMode::Find);
93 }
94
95 #[test]
96 fn test_find_replace_mode_equality() {
97 assert_eq!(FindReplaceMode::Find, FindReplaceMode::Find);
98 assert_eq!(FindReplaceMode::Replace, FindReplaceMode::Replace);
99 assert_ne!(FindReplaceMode::Find, FindReplaceMode::Replace);
100 }
101
102 #[test]
107 fn test_find_options_default() {
108 let opts = FindOptions::default();
109 assert!(!opts.case_sensitive);
110 assert!(!opts.whole_word);
111 assert!(!opts.use_regex);
112 }
113
114 #[test]
115 fn test_find_options_custom() {
116 let opts = FindOptions {
117 case_sensitive: true,
118 whole_word: true,
119 use_regex: false,
120 };
121 assert!(opts.case_sensitive);
122 assert!(opts.whole_word);
123 assert!(!opts.use_regex);
124 }
125
126 #[test]
131 fn test_find_match_new() {
132 let start = CursorPos { line: 0, col: 5 };
133 let end = CursorPos { line: 0, col: 10 };
134 let match_result = FindMatch::new(start, end);
135
136 assert_eq!(match_result.start.line, 0);
137 assert_eq!(match_result.start.col, 5);
138 assert_eq!(match_result.end.line, 0);
139 assert_eq!(match_result.end.col, 10);
140 }
141
142 #[test]
143 fn test_find_match_equality() {
144 let m1 = FindMatch::new(CursorPos { line: 0, col: 0 }, CursorPos { line: 0, col: 5 });
145 let m2 = FindMatch::new(CursorPos { line: 0, col: 0 }, CursorPos { line: 0, col: 5 });
146 let m3 = FindMatch::new(CursorPos { line: 1, col: 0 }, CursorPos { line: 1, col: 5 });
147
148 assert_eq!(m1, m2);
149 assert_ne!(m1, m3);
150 }
151
152 #[test]
153 fn test_find_match_clone() {
154 let m = FindMatch::new(CursorPos { line: 2, col: 3 }, CursorPos { line: 2, col: 8 });
155 let cloned = m.clone();
156 assert_eq!(m, cloned);
157 }
158
159 #[test]
164 fn test_find_replace_state_default() {
165 let state = FindReplaceState::default();
166 assert!(state.query.is_empty());
167 assert!(state.replace_with.is_empty());
168 assert!(state.matches.is_empty());
169 assert!(state.current_match.is_none());
170 assert_eq!(state.mode, FindReplaceMode::Find);
171 }
172
173 #[test]
174 fn test_find_replace_state_new_find() {
175 let state = FindReplaceState::new(FindReplaceMode::Find);
176 assert_eq!(state.mode, FindReplaceMode::Find);
177 assert!(state.query_focused);
178 }
179
180 #[test]
181 fn test_find_replace_state_new_replace() {
182 let state = FindReplaceState::new(FindReplaceMode::Replace);
183 assert_eq!(state.mode, FindReplaceMode::Replace);
184 assert!(state.query_focused);
185 }
186
187 #[test]
188 fn test_find_replace_state_match_count_empty() {
189 let state = FindReplaceState::default();
190 assert_eq!(state.match_count(), 0);
191 }
192
193 #[test]
194 fn test_find_replace_state_match_count() {
195 let mut state = FindReplaceState::default();
196 state.matches = vec![
197 FindMatch::new(CursorPos { line: 0, col: 0 }, CursorPos { line: 0, col: 5 }),
198 FindMatch::new(CursorPos { line: 1, col: 0 }, CursorPos { line: 1, col: 5 }),
199 FindMatch::new(CursorPos { line: 2, col: 0 }, CursorPos { line: 2, col: 5 }),
200 ];
201 assert_eq!(state.match_count(), 3);
202 }
203
204 #[test]
205 fn test_find_replace_state_current_match_display_none() {
206 let state = FindReplaceState::default();
207 assert_eq!(state.current_match_display(), 0);
208 }
209
210 #[test]
211 fn test_find_replace_state_current_match_display() {
212 let mut state = FindReplaceState::default();
213 state.matches = vec![
214 FindMatch::new(CursorPos { line: 0, col: 0 }, CursorPos { line: 0, col: 5 }),
215 FindMatch::new(CursorPos { line: 1, col: 0 }, CursorPos { line: 1, col: 5 }),
216 ];
217 state.current_match = Some(0);
218 assert_eq!(state.current_match_display(), 1);
219
220 state.current_match = Some(1);
221 assert_eq!(state.current_match_display(), 2);
222 }
223
224 #[test]
225 fn test_find_replace_state_query_focused() {
226 let state = FindReplaceState::new(FindReplaceMode::Find);
227 assert!(state.query_focused);
228 }
229
230 #[test]
231 fn test_find_replace_state_with_data() {
232 let mut state = FindReplaceState::new(FindReplaceMode::Replace);
233 state.query = "search".to_string();
234 state.replace_with = "replace".to_string();
235 state.options = FindOptions {
236 case_sensitive: true,
237 whole_word: false,
238 use_regex: false,
239 };
240
241 assert_eq!(state.query, "search");
242 assert_eq!(state.replace_with, "replace");
243 assert!(state.options.case_sensitive);
244 }
245}