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 KeyCode::Home => {
191 self.preview.scroll = 0;
192 }
193 KeyCode::End => {
194 self.preview.scroll = self.preview.max_scroll;
195 }
196 _ => {}
197 }
198 }
199
200 pub fn handle_key_confirmation_mode(&mut self, key: KeyEvent) {
201 match key.code {
202 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Char('l') | KeyCode::Right => {
203 if self.log_mode {
204 if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
205 info!(
206 "Confirmed execution of {} scripts.",
207 self.multi_select.scripts.len()
208 );
209 } else if let Some(script_path) = self.get_script_path() {
210 info!("Confirmed execution of script: {}", script_path.display());
211 }
212 }
213
214 if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
215 self.script_execution_queue = self.multi_select.scripts.clone().into();
216 } else if let Some(script_path) = self.get_script_path() {
217 self.script_execution_queue.push_back(script_path);
218 }
219
220 if !self.script_execution_queue.is_empty() {
221 let script_path = self
222 .script_execution_queue
223 .pop_front()
224 .expect("queue checked is_empty above");
225 match RunScriptPopup::new(script_path, self.log_mode, self.theme.clone()) {
226 Ok(popup) => {
227 self.run_script_popup = Some(popup);
228 self.mode = AppMode::RunScript;
229 }
230 Err(e) => {
231 log::error!("Failed to start script popup: {e}");
232 self.run_script_popup = None;
233 self.mode = AppMode::Normal;
234 }
235 }
236 } else {
237 self.mode = AppMode::Normal;
238 }
239 }
240 KeyCode::Char('n')
241 | KeyCode::Char('N')
242 | KeyCode::Esc
243 | KeyCode::Char('h')
244 | KeyCode::Char('q')
245 | KeyCode::Left => {
246 if self.log_mode {
247 info!("Cancelled script execution.");
248 }
249 self.mode = AppMode::Normal;
250 }
251 _ => {}
252 }
253 }
254
255 pub fn handle_key_help_mode(&mut self, key: KeyEvent) {
256 match key.code {
257 KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => {
258 self.mode = AppMode::Normal;
259 self.help.scroll = 0;
260 }
261 KeyCode::Char('j') | KeyCode::Down => {
262 self.help.scroll = (self.help.scroll + 1).min(self.help.max_scroll);
263 }
264 KeyCode::Char('k') | KeyCode::Up => {
265 self.help.scroll = self.help.scroll.saturating_sub(1);
266 }
267 KeyCode::Home => {
268 self.help.scroll = 0;
269 }
270 KeyCode::End => {
271 self.help.scroll = self.help.max_scroll;
272 }
273 KeyCode::PageDown => {
274 self.help.scroll = (self.help.scroll + 10).min(self.help.max_scroll);
275 }
276 KeyCode::PageUp => {
277 self.help.scroll = self.help.scroll.saturating_sub(10);
278 }
279 _ => {}
280 }
281 }
282
283 pub fn handle_key_description_mode(&mut self, key: KeyEvent) {
284 match key.code {
285 KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('d') => {
286 self.mode = AppMode::Normal;
287 self.description.content = None;
288 self.description.scroll = 0;
289 }
290 KeyCode::Char('j') | KeyCode::Down => {
291 self.description.scroll =
292 (self.description.scroll + 1).min(self.description.max_scroll);
293 }
294 KeyCode::Char('k') | KeyCode::Up => {
295 self.description.scroll = self.description.scroll.saturating_sub(1);
296 }
297 _ => {}
298 }
299 }
300}