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}