summaryrefslogblamecommitdiff
path: root/makima/src/daemon/tui/event.rs
blob: d5ca569af936980624002016a21a72e02135191c (plain) (tree)
1
2
3
4
5
6




                                                                     
                                                                    











                                                                        




                                                                                     
                          
                                                          

                                                       
                                                                                  
                                                                                             



                                               
                                                           












                                                           






                                                                   



                                                                  


                                                                                          


                                                                 


                                                  


                                              


                                           




















































                                                                                    



























































                                                                                 









                                                           























                                                                         

































                                                                                                        


                                                       
                                                                                                                      

                                                                                           
                                                                                                                           
                                                                                                                                        

     




                                                                
//! 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<Option<Event>> {
    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"
}