Skip to main content

carch_core/ui/
events.rs

1use crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind};
2use log::info;
3
4use super::popups::run_script::RunScriptPopup;
5use super::state::{App, AppMode, FocusedPanel};
6
7impl<'a> App<'a> {
8    pub fn handle_search_input(&mut self, key: KeyEvent) {
9        match key.code {
10            KeyCode::Esc => self.toggle_search_mode(),
11            KeyCode::Enter if !self.search.results.is_empty() => {
12                let selected_item = self.search.results[self.search.selected_idx].clone();
13                if self.log_mode {
14                    info!(
15                        "Selected script from search: {}/{}",
16                        selected_item.item.category, selected_item.item.name
17                    );
18                }
19
20                if let Some(category_idx) =
21                    self.categories.items.iter().position(|c| *c == selected_item.item.category)
22                {
23                    self.categories.state.select(Some(category_idx));
24                    self.update_script_list();
25
26                    if let Some(script_idx) =
27                        self.scripts.items.iter().position(|s| s.name == selected_item.item.name)
28                    {
29                        self.scripts.state.select(Some(script_idx));
30                    }
31                }
32
33                self.update_preview();
34                self.toggle_search_mode();
35                self.focused_panel = FocusedPanel::Scripts;
36                self.mode = AppMode::Normal;
37            }
38            KeyCode::Down if !self.search.results.is_empty() => {
39                self.search.selected_idx =
40                    (self.search.selected_idx + 1) % self.search.results.len();
41            }
42            KeyCode::Up if !self.search.results.is_empty() => {
43                self.search.selected_idx = if self.search.selected_idx > 0 {
44                    self.search.selected_idx - 1
45                } else {
46                    self.search.results.len() - 1
47                };
48            }
49            KeyCode::Tab => {
50                if let Some(autocomplete) = self.search.autocomplete.take() {
51                    self.search.input = autocomplete;
52                    self.search.cursor_position = self.search.input.len();
53                    self.perform_search();
54                    self.update_autocomplete();
55                }
56            }
57            KeyCode::Char(c) => {
58                self.search.input.push(c);
59                self.search.cursor_position += 1;
60                self.perform_search();
61                self.update_autocomplete();
62                self.search.selected_idx = 0;
63            }
64            KeyCode::Backspace if self.search.cursor_position > 0 => {
65                self.search.input.remove(self.search.cursor_position - 1);
66                self.search.cursor_position -= 1;
67                self.perform_search();
68                self.update_autocomplete();
69                self.search.selected_idx = 0;
70            }
71            KeyCode::Left => {
72                if self.search.cursor_position > 0 {
73                    self.search.cursor_position -= 1;
74                }
75                self.search.autocomplete = None;
76            }
77            KeyCode::Right => {
78                if self.search.cursor_position < self.search.input.len() {
79                    self.search.cursor_position += 1;
80                }
81                if self.search.cursor_position == self.search.input.len()
82                    && self.search.autocomplete.is_some()
83                {
84                    self.search.input = self.search.autocomplete.take().unwrap();
85                    self.search.cursor_position = self.search.input.len();
86                    self.perform_search();
87                }
88            }
89            _ => {}
90        }
91    }
92
93    pub fn handle_key_normal_mode(&mut self, key: KeyEvent) {
94        match key.code {
95            KeyCode::Char('q') => {
96                if self.multi_select.enabled {
97                    self.toggle_multi_select_mode();
98                } else {
99                    self.quit = true;
100                }
101            }
102            KeyCode::Esc if self.multi_select.enabled => {
103                self.toggle_multi_select_mode();
104            }
105            KeyCode::Char('j') | KeyCode::Down => {
106                self.next();
107            }
108            KeyCode::Char('k') | KeyCode::Up => {
109                self.previous();
110            }
111            KeyCode::Char('h') | KeyCode::Left => {
112                self.focused_panel = FocusedPanel::Categories;
113                self.scripts.state.select(None);
114            }
115            KeyCode::Char('l') | KeyCode::Right => {
116                if self.focused_panel == FocusedPanel::Scripts {
117                    if self.scripts.state.selected().is_some()
118                        && !(self.multi_select.enabled && self.multi_select.scripts.is_empty())
119                    {
120                        self.mode = AppMode::Confirm;
121                    }
122                } else {
123                    self.focused_panel = FocusedPanel::Scripts;
124                    if !self.scripts.items.is_empty() {
125                        self.scripts.state.select(Some(0));
126                    }
127                }
128            }
129            KeyCode::Home => {
130                self.top();
131            }
132            KeyCode::End => {
133                self.bottom();
134            }
135            KeyCode::Char('/') => {
136                self.toggle_search_mode();
137            }
138            KeyCode::Char('p') => {
139                self.toggle_preview_mode();
140            }
141            KeyCode::Char('m') => {
142                self.toggle_multi_select_mode();
143            }
144            KeyCode::Char('?') => {
145                self.toggle_help_mode();
146            }
147            KeyCode::Char('d') => {
148                self.toggle_description_popup();
149            }
150            KeyCode::Char('t') if !self.theme_locked => {
151                self.cycle_theme();
152            }
153            KeyCode::Enter
154                if self.focused_panel == FocusedPanel::Scripts
155                    && self.scripts.state.selected().is_some()
156                    && !(self.multi_select.enabled && self.multi_select.scripts.is_empty()) =>
157            {
158                self.mode = AppMode::Confirm;
159            }
160            KeyCode::Char(' ') if self.multi_select.enabled => {
161                self.toggle_script_selection();
162            }
163            _ => {}
164        }
165    }
166
167    pub fn handle_key_preview_mode(&mut self, key: KeyEvent) {
168        match key.code {
169            KeyCode::Char('q') | KeyCode::Esc | KeyCode::Char('p') | KeyCode::Char('h') => {
170                self.mode = AppMode::Normal;
171            }
172            KeyCode::Char('j') | KeyCode::Down => {
173                self.scroll_preview_down();
174            }
175            KeyCode::Char('k') | KeyCode::Up => {
176                self.scroll_preview_up();
177            }
178            KeyCode::PageDown => {
179                self.scroll_preview_page_down();
180            }
181            KeyCode::PageUp => {
182                self.scroll_preview_page_up();
183            }
184            KeyCode::Home => {
185                self.preview.scroll = 0;
186            }
187            KeyCode::End => {
188                self.preview.scroll = self.preview.max_scroll;
189            }
190            _ => {}
191        }
192    }
193
194    pub fn handle_mouse(&mut self, event: MouseEvent) {
195        match event.kind {
196            MouseEventKind::ScrollDown => match self.mode {
197                AppMode::Normal => self.next(),
198                AppMode::Preview => {
199                    for _ in 0..2 {
200                        self.scroll_preview_down();
201                    }
202                }
203                AppMode::Search => {
204                    if !self.search.results.is_empty() {
205                        self.search.selected_idx =
206                            (self.search.selected_idx + 1) % self.search.results.len();
207                    }
208                }
209                AppMode::Confirm => {}
210                AppMode::Help => {
211                    self.help.scroll = self.help.scroll.saturating_add(2).min(self.help.max_scroll);
212                }
213                AppMode::Description => {}
214                AppMode::RunScript => {}
215                AppMode::RootWarning => {}
216            },
217            MouseEventKind::ScrollUp => match self.mode {
218                AppMode::Normal => self.previous(),
219                AppMode::Preview => {
220                    for _ in 0..2 {
221                        self.scroll_preview_up();
222                    }
223                }
224                AppMode::Search => {
225                    if !self.search.results.is_empty() {
226                        self.search.selected_idx = if self.search.selected_idx > 0 {
227                            self.search.selected_idx - 1
228                        } else {
229                            self.search.results.len() - 1
230                        };
231                    }
232                }
233                AppMode::Confirm => {}
234                AppMode::Help => {
235                    self.help.scroll = self.help.scroll.saturating_sub(2);
236                }
237                AppMode::Description => {}
238                AppMode::RunScript => {}
239                AppMode::RootWarning => {}
240            },
241            _ => {}
242        }
243    }
244
245    pub fn handle_key_confirmation_mode(&mut self, key: KeyEvent) {
246        match key.code {
247            KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Char('l') | KeyCode::Right => {
248                if self.log_mode {
249                    if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
250                        info!(
251                            "Confirmed execution of {} scripts.",
252                            self.multi_select.scripts.len()
253                        );
254                    } else if let Some(script_path) = self.get_script_path() {
255                        info!("Confirmed execution of script: {}", script_path.display());
256                    }
257                }
258
259                if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
260                    self.script_execution_queue = self.multi_select.scripts.clone();
261                } else if let Some(script_path) = self.get_script_path() {
262                    self.script_execution_queue.push(script_path);
263                }
264
265                if !self.script_execution_queue.is_empty() {
266                    let script_path = self.script_execution_queue.remove(0);
267                    let popup = RunScriptPopup::new(script_path, self.log_mode, self.theme.clone());
268                    self.run_script_popup = Some(popup);
269                    self.mode = AppMode::RunScript;
270                } else {
271                    self.mode = AppMode::Normal;
272                }
273            }
274            KeyCode::Char('n')
275            | KeyCode::Char('N')
276            | KeyCode::Esc
277            | KeyCode::Char('h')
278            | KeyCode::Char('q')
279            | KeyCode::Left => {
280                if self.log_mode {
281                    info!("Cancelled script execution.");
282                }
283                self.mode = AppMode::Normal;
284            }
285            _ => {}
286        }
287    }
288
289    pub fn handle_key_help_mode(&mut self, key: KeyEvent) {
290        match key.code {
291            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => {
292                self.mode = AppMode::Normal;
293                self.help.scroll = 0;
294            }
295            KeyCode::Char('j') | KeyCode::Down => {
296                self.help.scroll = (self.help.scroll + 1).min(self.help.max_scroll);
297            }
298            KeyCode::Char('k') | KeyCode::Up => {
299                self.help.scroll = self.help.scroll.saturating_sub(1);
300            }
301            KeyCode::Home => {
302                self.help.scroll = 0;
303            }
304            KeyCode::End => {
305                self.help.scroll = self.help.max_scroll;
306            }
307            KeyCode::PageDown => {
308                self.help.scroll = (self.help.scroll + 10).min(self.help.max_scroll);
309            }
310            KeyCode::PageUp => {
311                self.help.scroll = self.help.scroll.saturating_sub(10);
312            }
313            _ => {}
314        }
315    }
316
317    pub fn handle_key_description_mode(&mut self, key: KeyEvent) {
318        match key.code {
319            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('d') => {
320                self.mode = AppMode::Normal;
321                self.description.content = None;
322                self.description.scroll = 0;
323            }
324            KeyCode::Char('j') | KeyCode::Down => {
325                self.description.scroll =
326                    (self.description.scroll + 1).min(self.description.max_scroll);
327            }
328            KeyCode::Char('k') | KeyCode::Up => {
329                self.description.scroll = self.description.scroll.saturating_sub(1);
330            }
331            _ => {}
332        }
333    }
334}