1use crate::app::AppState;
2use crate::app::InputMode;
3use ratatui::style::{Modifier, Style};
4
5impl AppState<'_> {
6 pub fn start_search_forward(&mut self) {
7 self.input_mode = InputMode::SearchForward;
8 self.input_buffer = String::new();
9
10 let mut text_area = tui_textarea::TextArea::default();
12 text_area.set_cursor_line_style(Style::default());
13 text_area.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
14 self.text_area = text_area;
15
16 self.add_notification("Search forward mode".to_string());
17 self.highlight_enabled = true;
18 }
19
20 pub fn start_search_backward(&mut self) {
21 self.input_mode = InputMode::SearchBackward;
22 self.input_buffer = String::new();
23
24 let mut text_area = tui_textarea::TextArea::default();
26 text_area.set_cursor_line_style(Style::default());
27 text_area.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED));
28 self.text_area = text_area;
29
30 self.add_notification("Search backward mode".to_string());
31 self.highlight_enabled = true;
32 }
33
34 pub fn execute_search(&mut self) {
35 let query = self.text_area.lines().join("\n");
36 self.input_buffer.clone_from(&query);
37
38 if query.is_empty() {
39 self.input_mode = InputMode::Normal;
40 return;
41 }
42
43 self.search_query.clone_from(&query);
45
46 match self.input_mode {
48 InputMode::SearchForward => self.search_direction = true,
49 InputMode::SearchBackward => self.search_direction = false,
50 _ => {}
51 }
52
53 self.search_results = self.find_all_matches(&query);
54
55 if self.search_results.is_empty() {
56 self.add_notification(format!("Pattern not found: {query}"));
57 self.current_search_idx = None;
58 } else {
59 self.jump_to_next_search_result();
61 self.add_notification(format!(
62 "{} matches found for: {}",
63 self.search_results.len(),
64 query
65 ));
66 }
67
68 self.input_mode = InputMode::Normal;
69 self.input_buffer = String::new();
70 self.text_area = tui_textarea::TextArea::default();
71 }
72
73 pub fn find_all_matches(&self, query: &str) -> Vec<(usize, usize)> {
74 let sheet = self.workbook.get_current_sheet();
75 let query_lower = query.to_lowercase();
76
77 let mut results = Vec::with_capacity(32);
79
80 for row in 1..=sheet.max_rows {
82 for col in 1..=sheet.max_cols {
83 if row < sheet.data.len() && col < sheet.data[row].len() {
84 let cell_content = &sheet.data[row][col].value;
85
86 if cell_content.is_empty() {
87 continue;
88 }
89
90 if Self::case_insensitive_contains(cell_content, &query_lower) {
91 results.push((row, col));
92 }
93 }
94 }
95 }
96
97 results
98 }
99
100 fn case_insensitive_contains(haystack: &str, needle: &str) -> bool {
101 if needle.is_empty() {
102 return true;
103 }
104 if haystack.is_empty() {
105 return false;
106 }
107
108 haystack.to_lowercase().contains(needle)
109 }
110
111 pub fn jump_to_next_search_result(&mut self) {
112 if self.search_results.is_empty() {
113 return;
114 }
115
116 self.highlight_enabled = true;
117
118 let current_pos = self.selected_cell;
119
120 if self.search_direction {
121 let next_idx = self.search_results.iter().position(|&pos| {
123 pos.0 > current_pos.0 || (pos.0 == current_pos.0 && pos.1 > current_pos.1)
124 });
125
126 if let Some(idx) = next_idx {
127 self.current_search_idx = Some(idx);
128 self.selected_cell = self.search_results[idx];
129 } else {
130 self.current_search_idx = Some(0);
132 self.selected_cell = self.search_results[0];
133 self.add_notification("Search wrapped to top".to_string());
134 }
135 } else {
136 let prev_idx = self.search_results.iter().rposition(|&pos| {
138 pos.0 < current_pos.0 || (pos.0 == current_pos.0 && pos.1 < current_pos.1)
139 });
140
141 if let Some(idx) = prev_idx {
142 self.current_search_idx = Some(idx);
143 self.selected_cell = self.search_results[idx];
144 } else {
145 let last_idx = self.search_results.len() - 1;
147 self.current_search_idx = Some(last_idx);
148 self.selected_cell = self.search_results[last_idx];
149 self.add_notification("Search wrapped to bottom".to_string());
150 }
151 }
152
153 self.handle_scrolling();
154 }
155
156 pub fn jump_to_prev_search_result(&mut self) {
157 if self.search_results.is_empty() {
158 return;
159 }
160
161 self.search_direction = !self.search_direction;
163 self.jump_to_next_search_result();
164 self.search_direction = !self.search_direction;
166 }
167
168 pub fn disable_search_highlight(&mut self) {
169 self.highlight_enabled = false;
170 self.add_notification("Search highlighting disabled".to_string());
171 }
172}