Skip to main content

git_same/tui/
mod.rs

1//! Full-screen TUI for git-same.
2//!
3//! Launched when `gisa` is run with no subcommand.
4
5pub mod app;
6pub mod backend;
7pub mod event;
8pub mod handler;
9pub mod screens;
10pub mod ui;
11pub mod widgets;
12
13use crate::config::{Config, WorkspaceManager};
14use crate::errors::Result;
15use app::App;
16use crossterm::{
17    execute,
18    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
19};
20use ratatui::backend::CrosstermBackend;
21use ratatui::Terminal;
22use std::io;
23use std::time::Duration;
24
25/// Run the TUI application.
26pub async fn run_tui(config: Config, config_was_created: bool) -> Result<()> {
27    // Setup terminal
28    enable_raw_mode()?;
29    let mut stdout = io::stdout();
30    if let Err(e) = execute!(stdout, EnterAlternateScreen) {
31        let _ = disable_raw_mode();
32        return Err(e.into());
33    }
34    let backend = CrosstermBackend::new(stdout);
35    let mut terminal = match Terminal::new(backend) {
36        Ok(terminal) => terminal,
37        Err(e) => {
38            let _ = disable_raw_mode();
39            let mut stdout = io::stdout();
40            let _ = execute!(stdout, LeaveAlternateScreen);
41            return Err(e.into());
42        }
43    };
44
45    let result = async {
46        // Load workspaces
47        let workspaces = WorkspaceManager::list()?;
48
49        // Create app state
50        let mut app = App::new(config, workspaces, config_was_created);
51
52        // Start event loop
53        let tick_rate = Duration::from_millis(100);
54        let (mut rx, backend_tx) = event::spawn_event_loop(tick_rate);
55
56        // Main loop
57        run_app(&mut terminal, &mut app, &mut rx, &backend_tx).await
58    }
59    .await;
60
61    // Restore terminal (always, even on error)
62    let _ = disable_raw_mode();
63    let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
64    let _ = terminal.show_cursor();
65
66    result
67}
68
69async fn run_app(
70    terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
71    app: &mut App,
72    rx: &mut tokio::sync::mpsc::UnboundedReceiver<event::AppEvent>,
73    backend_tx: &tokio::sync::mpsc::UnboundedSender<event::AppEvent>,
74) -> Result<()> {
75    loop {
76        terminal.draw(|frame| ui::render(app, frame))?;
77
78        match rx.recv().await {
79            Some(event) => handler::handle_event(app, event, backend_tx).await,
80            None => break,
81        }
82
83        if app.should_quit {
84            break;
85        }
86    }
87    Ok(())
88}