Skip to main content

carch_core/ui/
events.rs

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