//! TUI event handling. use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; use std::time::Duration; use super::app::{Action, App, CreateFormField, InputMode, ViewType}; /// Poll for events with timeout pub fn poll_event(timeout: Duration) -> std::io::Result> { if event::poll(timeout)? { Ok(Some(event::read()?)) } else { Ok(None) } } /// Handle a key event and return the resulting action pub fn handle_key_event(app: &App, key: KeyEvent) -> Action { // Special handling for TaskOutput view if app.view_type == ViewType::TaskOutput && app.input_mode == InputMode::Normal { return handle_output_mode(key); } match app.input_mode { InputMode::Normal => handle_normal_mode(app, key), InputMode::Search => handle_search_mode(key), InputMode::Confirm => handle_confirm_mode(key), InputMode::EditName | InputMode::EditDescription => handle_edit_mode(key), InputMode::CreateName | InputMode::CreateDescription => handle_create_mode(app, key), } } /// Handle key events in normal navigation mode fn handle_normal_mode(app: &App, key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { match key.code { KeyCode::Char('c') => return Action::Quit, _ => {} } } match key.code { // Navigation KeyCode::Up | KeyCode::Char('k') => Action::Up, KeyCode::Down | KeyCode::Char('j') => Action::Down, // Drill-down into selected item (Enter or l for vim-style) KeyCode::Enter | KeyCode::Char('l') => Action::DrillDown, // Go back (Backspace, h for vim-style, or Esc) KeyCode::Backspace | KeyCode::Char('h') => Action::GoBack, // Other actions KeyCode::Char('e') => Action::Edit, KeyCode::Char('d') => Action::Delete, KeyCode::Char('c') => Action::Navigate, // cd to worktree // New contract (only in contracts view) KeyCode::Char('n') if app.view_type == ViewType::Contracts => Action::NewContract, // Preview toggle (space to show details in preview pane) KeyCode::Char(' ') => Action::Select, // Search KeyCode::Char('/') => Action::EnterSearch, // Refresh KeyCode::Char('r') => Action::Refresh, // Quit (only q, Esc now goes back) KeyCode::Char('q') => Action::Quit, KeyCode::Esc => Action::GoBack, _ => Action::None, } } /// Handle key events in search mode fn handle_search_mode(key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { match key.code { KeyCode::Char('c') => return Action::Quit, KeyCode::Char('u') => return Action::ClearSearch, _ => {} } } match key.code { // Exit search mode KeyCode::Esc => Action::ExitSearch, KeyCode::Enter => Action::ExitSearch, // Text input KeyCode::Char(c) => Action::SearchChar(c), KeyCode::Backspace => Action::SearchBackspace, // Navigation while searching KeyCode::Up => Action::Up, KeyCode::Down => Action::Down, _ => Action::None, } } /// Handle key events in confirmation mode fn handle_confirm_mode(key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { if let KeyCode::Char('c') = key.code { return Action::Quit; } } match key.code { // Confirm KeyCode::Char('y') | KeyCode::Char('Y') => Action::ConfirmYes, // Cancel KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => Action::ConfirmNo, _ => Action::None, } } /// Handle key events in task output view mode fn handle_output_mode(key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { if let KeyCode::Char('c') = key.code { return Action::Quit; } } match key.code { // Scroll KeyCode::Up | KeyCode::Char('k') => Action::ScrollUp, KeyCode::Down | KeyCode::Char('j') => Action::ScrollDown, KeyCode::PageUp => Action::ScrollUp, KeyCode::PageDown => Action::ScrollDown, // Scroll to bottom KeyCode::Char('G') | KeyCode::End => Action::ScrollToBottom, // Go back (Backspace, h for vim-style, q, or Esc) KeyCode::Backspace | KeyCode::Char('h') | KeyCode::Esc => Action::GoBack, KeyCode::Char('q') => Action::GoBack, // Refresh (re-connect WebSocket) KeyCode::Char('r') => Action::Refresh, // Navigate to worktree KeyCode::Char('c') => Action::Navigate, _ => Action::None, } } /// Handle key events in edit mode fn handle_edit_mode(key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { if let KeyCode::Char('c') = key.code { return Action::Quit; } } match key.code { // Save KeyCode::Enter => Action::EditSave, // Cancel KeyCode::Esc => Action::EditCancel, // Switch fields KeyCode::Tab => Action::EditNextField, // Text input KeyCode::Char(c) => Action::EditChar(c), KeyCode::Backspace => Action::EditBackspace, _ => Action::None, } } /// Handle key events in create contract mode fn handle_create_mode(app: &App, key: KeyEvent) -> Action { // Check for Ctrl+C first if key.modifiers.contains(KeyModifiers::CONTROL) { if let KeyCode::Char('c') = key.code { return Action::Quit; } } let current_field = app.create_state.current_field(); let has_suggestions = app.create_state.show_suggestions && !app.create_state.repo_suggestions.is_empty(); // Allow Ctrl+N/Ctrl+P to navigate suggestions from any field if has_suggestions && key.modifiers.contains(KeyModifiers::CONTROL) { match key.code { KeyCode::Char('n') => return Action::CreateNextSuggestion, KeyCode::Char('p') => return Action::CreatePrevSuggestion, _ => {} } } // Special handling when on Repository field with suggestions visible let on_repo_field = current_field == CreateFormField::Repository; if has_suggestions && on_repo_field { match key.code { // Up/Down navigate suggestions when on repo field KeyCode::Up => return Action::CreatePrevSuggestion, KeyCode::Down => return Action::CreateNextSuggestion, // Enter applies suggestion instead of submitting form KeyCode::Enter => return Action::CreateApplySuggestion, _ => {} } } match key.code { // Submit form KeyCode::Enter => { // If on contract type field, toggle instead of submit if current_field == CreateFormField::ContractType { Action::CreateToggle } else { Action::CreateSubmit } } // Cancel KeyCode::Esc => Action::CreateCancel, // Navigate between fields KeyCode::Tab => Action::CreateNextField, KeyCode::BackTab => Action::CreatePrevField, KeyCode::Up => Action::CreatePrevField, KeyCode::Down => Action::CreateNextField, // Toggle for contract type field KeyCode::Char(' ') if current_field == CreateFormField::ContractType => Action::CreateToggle, KeyCode::Left if current_field == CreateFormField::ContractType => Action::CreateToggle, KeyCode::Right if current_field == CreateFormField::ContractType => Action::CreateToggle, // Text input (for text fields) KeyCode::Char(c) if current_field != CreateFormField::ContractType => Action::CreateChar(c), KeyCode::Backspace if current_field != CreateFormField::ContractType => Action::CreateBackspace, _ => Action::None, } } /// Get help text for current mode pub fn get_help_text(mode: InputMode) -> &'static str { match mode { InputMode::Normal => "j/k: nav | Enter: open | Esc/h: back | e: edit | d: del | n: new | /: search | q: quit", InputMode::Search => "Type to search | Enter/Esc: exit search | Up/Down: navigate", InputMode::Confirm => "y: confirm | n/Esc: cancel", InputMode::EditName | InputMode::EditDescription => "Type to edit | Tab: switch field | Enter: save | Esc: cancel", InputMode::CreateName | InputMode::CreateDescription => "Type to edit | Tab/↑↓: switch field | Enter: create | Esc: cancel", } } /// Get help text for output view pub fn get_output_help_text() -> &'static str { "j/k: scroll | G: bottom | c: cd | q/Esc: back | r: refresh" }