summaryrefslogtreecommitdiff
path: root/makima/src/daemon/tui/event.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-19 13:47:32 +0000
committerGitHub <noreply@github.com>2026-01-19 13:47:32 +0000
commit0833fb1f30c0c3b920157deb882e0e902c3af02a (patch)
tree45110fb8cb9277dfbaccfeb53ed9c1f76975022b /makima/src/daemon/tui/event.rs
parent786510379bed060db2b3742b7dfca671552d2c34 (diff)
downloadsoryu-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.rs118
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",
+ }
+}