1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
//! 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 mod ws_client;
pub use app::{App, ListItem, ViewType, ViewState, InputMode, Action, OutputBuffer, OutputLine, OutputMessageType, WsConnectionState, CreateContractState, CreateFormField, RepositorySuggestion};
pub use ws_client::{TuiWsClient, WsCommand, WsEvent, TaskOutputEvent};
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);
}
|