summaryrefslogtreecommitdiff
path: root/makima/src/daemon/tui/mod.rs
blob: 46652ecbf4364f26b66d693a1067bbcdde6c56fd (plain) (blame)
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
//! 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::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);
}