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/mod.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/mod.rs')
| -rw-r--r-- | makima/src/daemon/tui/mod.rs | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/makima/src/daemon/tui/mod.rs b/makima/src/daemon/tui/mod.rs new file mode 100644 index 0000000..fd1d44d --- /dev/null +++ b/makima/src/daemon/tui/mod.rs @@ -0,0 +1,96 @@ +//! TUI module for interactive browsing. +//! +//! This module provides an interactive Terminal User Interface (TUI) for +//! browsing and managing tasks, contracts, and files in the makima system. +//! +//! # Features +//! +//! - **Fuzzy Search**: Real-time filtering with the SkimMatcherV2 algorithm +//! - **Keyboard Navigation**: Vim-style keybindings (j/k) and arrow keys +//! - **Preview Pane**: Side-by-side view of item details +//! - **Multiple Views**: Browse tasks, contracts, or files + +pub mod app; +pub mod event; +pub mod fuzzy; +pub mod ui; + +pub use app::{App, ListItem, ViewType, InputMode, Action}; +pub use fuzzy::FuzzyMatcher; + +use std::io; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::prelude::*; +use ratatui::backend::CrosstermBackend; + +pub type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>; + +/// Run the TUI application +pub fn run(mut app: App) -> Result<Option<String>, Box<dyn std::error::Error>> { + // Setup terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = ratatui::Terminal::new(backend)?; + + // Run the main loop + let result = run_app(&mut terminal, &mut app); + + // Cleanup terminal + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + result +} + +fn run_app( + terminal: &mut Terminal, + app: &mut App, +) -> Result<Option<String>, Box<dyn std::error::Error>> { + use crossterm::event::Event; + use std::time::Duration; + + loop { + terminal.draw(|f| ui::render(f, app))?; + + // Poll for events with 100ms timeout + if let Some(evt) = event::poll_event(Duration::from_millis(100))? { + if let Event::Key(key) = evt { + let action = event::handle_key_event(app, key); + match action { + Action::Quit => break, + Action::OutputPath(path) => return Ok(Some(path)), + Action::None => {} + _ => { + let result = app.handle_action(action); + // Check if handle_action returned a special action + if let Action::OutputPath(path) = result { + return Ok(Some(path)); + } + } + } + } + } + + if app.should_quit { + break; + } + } + + Ok(None) +} + +/// Print a path to stdout (for cd integration) +pub fn print_path(path: &str) { + println!("{}", path); +} |
