summaryrefslogtreecommitdiff
path: root/makima/src/daemon/tui/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/daemon/tui/mod.rs')
-rw-r--r--makima/src/daemon/tui/mod.rs96
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);
+}