carch-core 5.4.3

Core library for carch, providing script management and UI components.
Documentation
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use log::info;

use super::popups::run_script::RunScriptPopup;
use super::state::{App, AppMode, FocusedPanel};

impl App {
    pub fn handle_search_input(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Esc => {
                self.toggle_search_mode();
            }
            KeyCode::Enter if !self.search.results.is_empty() => {
                let selected_item = self.search.results[self.search.selected_idx].clone();
                if self.log_mode {
                    info!(
                        "Selected script from search: {}/{}",
                        selected_item.item.category, selected_item.item.name
                    );
                }

                if let Some(category_idx) =
                    self.categories.items.iter().position(|c| *c == selected_item.item.category)
                {
                    self.categories.state.select(Some(category_idx));
                    self.update_script_list();

                    if let Some(script_idx) =
                        self.scripts.items.iter().position(|s| s.name == selected_item.item.name)
                    {
                        self.scripts.state.select(Some(script_idx));
                    }
                }

                self.update_preview();
                self.toggle_search_mode();
                self.focused_panel = FocusedPanel::Scripts;
                self.mode = AppMode::Normal;
            }
            KeyCode::Down if !self.search.results.is_empty() => {
                self.search.selected_idx =
                    (self.search.selected_idx + 1) % self.search.results.len();
            }
            KeyCode::Up if !self.search.results.is_empty() => {
                self.search.selected_idx = if self.search.selected_idx > 0 {
                    self.search.selected_idx - 1
                } else {
                    self.search.results.len() - 1
                };
            }
            KeyCode::Tab => {
                if let Some(autocomplete) = self.search.autocomplete.take() {
                    self.search.input = autocomplete;
                    self.search.cursor_position = self.search.input.len();
                    self.perform_search();
                    self.update_autocomplete();
                }
            }
            KeyCode::Char(c) => {
                self.search.input.push(c);
                self.search.cursor_position += 1;
                self.perform_search();
                self.update_autocomplete();
                self.search.selected_idx = 0;
            }
            KeyCode::Backspace if self.search.cursor_position > 0 => {
                self.search.input.remove(self.search.cursor_position - 1);
                self.search.cursor_position -= 1;
                self.perform_search();
                self.update_autocomplete();
                self.search.selected_idx = 0;
            }
            KeyCode::Left => {
                if self.search.cursor_position > 0 {
                    self.search.cursor_position -= 1;
                }
                self.search.autocomplete = None;
            }
            KeyCode::Right => {
                if self.search.cursor_position < self.search.input.len() {
                    self.search.cursor_position += 1;
                }
                if self.search.cursor_position == self.search.input.len()
                    && let Some(autocomplete) = self.search.autocomplete.take()
                {
                    self.search.input = autocomplete;
                    self.search.cursor_position = self.search.input.len();
                    self.perform_search();
                }
            }
            _ => {}
        }
    }

    pub fn handle_key_normal_mode(&mut self, key: KeyEvent) {
        if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
            self.quit = true;
            return;
        }
        match key.code {
            KeyCode::Char('q') => {
                if self.multi_select.enabled {
                    self.toggle_multi_select_mode();
                } else {
                    self.quit = true;
                }
            }
            KeyCode::Esc if self.multi_select.enabled => {
                self.toggle_multi_select_mode();
            }
            KeyCode::Char('j') | KeyCode::Down => {
                self.next();
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.previous();
            }
            KeyCode::Char('h') | KeyCode::Left => {
                self.focused_panel = FocusedPanel::Categories;
                self.scripts.state.select(None);
            }
            KeyCode::Char('l') | KeyCode::Right => {
                if self.focused_panel == FocusedPanel::Scripts {
                    if self.scripts.state.selected().is_some()
                        && !(self.multi_select.enabled && self.multi_select.scripts.is_empty())
                    {
                        self.mode = AppMode::Confirm;
                    }
                } else {
                    self.focused_panel = FocusedPanel::Scripts;
                    if !self.scripts.items.is_empty() {
                        self.scripts.state.select(Some(0));
                    }
                }
            }
            KeyCode::Home => {
                self.top();
            }
            KeyCode::End => {
                self.bottom();
            }
            KeyCode::Char('/') => {
                self.toggle_search_mode();
            }
            KeyCode::Char('p') => {
                self.toggle_preview_mode();
            }
            KeyCode::Char('m') => {
                self.toggle_multi_select_mode();
            }
            KeyCode::Char('?') => {
                self.toggle_help_mode();
            }
            KeyCode::Char('d') => {
                self.toggle_description_popup();
            }
            KeyCode::Char('t') if !self.theme_locked => {
                self.cycle_theme();
            }
            KeyCode::Enter
                if self.focused_panel == FocusedPanel::Scripts
                    && self.scripts.state.selected().is_some()
                    && !(self.multi_select.enabled && self.multi_select.scripts.is_empty()) =>
            {
                self.mode = AppMode::Confirm;
            }
            KeyCode::Char(' ') if self.multi_select.enabled => {
                self.toggle_script_selection();
            }
            _ => {}
        }
    }

    pub fn handle_key_preview_mode(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Char('q') | KeyCode::Esc | KeyCode::Char('p') | KeyCode::Char('h') => {
                self.mode = AppMode::Normal;
            }
            KeyCode::Char('j') | KeyCode::Down => {
                self.scroll_preview_down();
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.scroll_preview_up();
            }
            KeyCode::PageDown => {
                self.scroll_preview_page_down();
            }
            KeyCode::PageUp => {
                self.scroll_preview_page_up();
            }
            _ => {}
        }
    }

    pub fn handle_key_confirmation_mode(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Char('l') | KeyCode::Right => {
                if self.log_mode {
                    if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
                        info!(
                            "Confirmed execution of {} scripts.",
                            self.multi_select.scripts.len()
                        );
                    } else if let Some(script_path) = self.get_script_path() {
                        info!("Confirmed execution of script: {}", script_path.display());
                    }
                }

                if self.multi_select.enabled && !self.multi_select.scripts.is_empty() {
                    self.script_execution_queue = self.multi_select.scripts.clone().into();
                } else if let Some(script_path) = self.get_script_path() {
                    self.script_execution_queue.push_back(script_path);
                }

                if !self.script_execution_queue.is_empty() {
                    let script_path = self
                        .script_execution_queue
                        .pop_front()
                        .expect("queue checked is_empty above");
                    match RunScriptPopup::new(script_path, self.log_mode, self.theme.clone()) {
                        Ok(popup) => {
                            self.run_script_popup = Some(popup);
                            self.mode = AppMode::RunScript;
                        }
                        Err(e) => {
                            log::error!("Failed to start script popup: {e}");
                            self.run_script_popup = None;
                            self.mode = AppMode::Normal;
                        }
                    }
                } else {
                    self.mode = AppMode::Normal;
                }
            }
            KeyCode::Char('n')
            | KeyCode::Char('N')
            | KeyCode::Esc
            | KeyCode::Char('h')
            | KeyCode::Char('q')
            | KeyCode::Left => {
                if self.log_mode {
                    info!("Cancelled script execution.");
                }
                self.mode = AppMode::Normal;
            }
            _ => {}
        }
    }

    pub fn handle_key_help_mode(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => {
                self.mode = AppMode::Normal;
                self.help.scroll = 0;
            }
            KeyCode::Char('j') | KeyCode::Down => {
                self.help.scroll = (self.help.scroll + 1).min(self.help.max_scroll);
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.help.scroll = self.help.scroll.saturating_sub(1);
            }
            KeyCode::Home => {
                self.help.scroll = 0;
            }
            KeyCode::End => {
                self.help.scroll = self.help.max_scroll;
            }
            KeyCode::PageDown => {
                self.help.scroll = (self.help.scroll + 10).min(self.help.max_scroll);
            }
            KeyCode::PageUp => {
                self.help.scroll = self.help.scroll.saturating_sub(10);
            }
            _ => {}
        }
    }

    pub fn handle_key_description_mode(&mut self, key: KeyEvent) {
        match key.code {
            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('d') => {
                self.mode = AppMode::Normal;
                self.description.content = None;
                self.description.scroll = 0;
            }
            KeyCode::Char('j') | KeyCode::Down => {
                self.description.scroll =
                    (self.description.scroll + 1).min(self.description.max_scroll);
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.description.scroll = self.description.scroll.saturating_sub(1);
            }
            _ => {}
        }
    }
}