diff options
| author | soryu <soryu@soryu.co> | 2026-01-19 13:47:32 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-19 13:47:32 +0000 |
| commit | 0833fb1f30c0c3b920157deb882e0e902c3af02a (patch) | |
| tree | 45110fb8cb9277dfbaccfeb53ed9c1f76975022b /makima/src/daemon/tui/event.rs | |
| parent | 786510379bed060db2b3742b7dfca671552d2c34 (diff) | |
| download | soryu-0833fb1f30c0c3b920157deb882e0e902c3af02a.tar.gz soryu-0833fb1f30c0c3b920157deb882e0e902c3af02a.zip | |
Add interactive TUI browser for tasks, contracts, and files (makima view) (#7)
* feat(tui): Implement fuzzy search with real-time filtering and highlighting
Adds comprehensive fuzzy search functionality to the TUI browser:
## Fuzzy Matching (fuzzy.rs)
- FuzzyMatcher wrapper using SkimMatcherV2 from fuzzy-matcher crate
- fuzzy_match() returns score and matched character indices
- fuzzy_match_all() supports multi-term search (space-separated)
- Recency-aware scoring to boost recent items in results
- Unit tests for all matching scenarios
## App State (app.rs)
- FilteredItem struct with index, score, and matched_indices
- apply_filter() uses fuzzy matching with score-based sorting
- match_count() and has_no_matches() helper methods
- Results sorted by match score (highest first)
## List View (list_view.rs)
- Highlighted matched characters in search results
- Yellow bold styling for matched chars
- Status icons with color coding
## Search Input (search_input.rs)
- Real-time match count display (X/Y matches)
- Visual feedback for no matches (red border)
- Placeholder text when search is empty
- Active search mode indication (yellow border)
## Event Handling (event.rs)
- Arrow key navigation while in search mode
- Ctrl+K/J for vim-style navigation during search
- Delete key support alongside backspace
- Ctrl+U to clear search query
- Tab toggles preview while searching
- Escape clears search and exits search mode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Task completion checkpoint
* [WIP] Heartbeat checkpoint - 2026-01-19 11:20:34 UTC
* Task completion checkpoint
* [WIP] Heartbeat checkpoint - 2026-01-19 11:31:19 UTC
* Task completion checkpoint
* [WIP] Heartbeat checkpoint - 2026-01-19 11:39:07 UTC
* fix(tui): Fix module exports and main binary integration
- Update mod.rs to properly export app, event, fuzzy, and ui modules
- Add run() function for TUI entry point
- Fix run_view() to use ViewCommand enum instead of ViewArgs
- Fix event handling to use poll_event and handle_key_event
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/src/daemon/tui/event.rs')
| -rw-r--r-- | makima/src/daemon/tui/event.rs | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/makima/src/daemon/tui/event.rs b/makima/src/daemon/tui/event.rs new file mode 100644 index 0000000..12a6890 --- /dev/null +++ b/makima/src/daemon/tui/event.rs @@ -0,0 +1,118 @@ +//! TUI event handling. + +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; +use std::time::Duration; + +use super::app::{Action, App, InputMode}; + +/// 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 { + match app.input_mode { + InputMode::Normal => handle_normal_mode(key), + InputMode::Search => handle_search_mode(key), + InputMode::Confirm => handle_confirm_mode(key), + } +} + +/// Handle key events in normal navigation mode +fn handle_normal_mode(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, + + // Actions + KeyCode::Enter => Action::Select, + KeyCode::Char('e') => Action::Edit, + KeyCode::Char('d') => Action::Delete, + KeyCode::Char('c') => Action::Navigate, // cd to worktree + + // Search + KeyCode::Char('/') => Action::EnterSearch, + + // Preview toggle (space to toggle preview visibility) + KeyCode::Char(' ') => Action::Select, + + // Refresh + KeyCode::Char('r') => Action::Refresh, + + // Quit + KeyCode::Char('q') | KeyCode::Esc => Action::Quit, + + _ => 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, + } +} + +/// Get help text for current mode +pub fn get_help_text(mode: InputMode) -> &'static str { + match mode { + InputMode::Normal => "j/k: navigate | Enter: details | e: edit | d: delete | c: cd | /: search | q: quit", + InputMode::Search => "Type to search | Enter/Esc: exit search | Up/Down: navigate", + InputMode::Confirm => "y: confirm | n/Esc: cancel", + } +} |
