jufu 0.1.0

Jujutsu log viewer TUI inspired by keifu
mod app;
mod jj;
mod model;
mod ui;

use std::time::Duration;

use anyhow::Result;
use clap::Parser;
use crossterm::event;

use crate::app::{App, BackgroundEvent, CommandSender, ControlFlow};
use crate::model::AppConfig;

#[derive(Debug, Parser)]
#[command(version, about = "Jujutsu log viewer TUI inspired by keifu")]
struct Cli {}

#[tokio::main]
async fn main() -> Result<()> {
    let _ = Cli::parse();

    let cwd = std::env::current_dir()?;
    jj::ensure_jj_available(&cwd).await?;
    let config = AppConfig { repo_path: cwd };

    let mut terminal = ui::init_terminal()?;
    let result = run(&mut terminal, config).await;
    let restore_result = ui::restore_terminal();
    restore_result?;
    result
}

async fn run(terminal: &mut ui::Terminal, config: AppConfig) -> Result<()> {
    let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::<BackgroundEvent>();
    let mut app = App::new(config);

    spawn_log_load(&sender, app.config().repo_path.clone());

    loop {
        while let Ok(message) = receiver.try_recv() {
            app.apply_background_event(message, &sender);
        }

        terminal.draw(|frame| ui::render(frame, &mut app))?;

        if event::poll(Duration::from_millis(100))? {
            let event = event::read()?;
            if matches!(app.handle_event(event, &sender), ControlFlow::Exit) {
                break;
            }
        }
    }

    Ok(())
}

fn spawn_log_load(sender: &CommandSender, repo_path: std::path::PathBuf) {
    let tx = sender.clone();
    tokio::spawn(async move {
        let result = jj::load_logs(&repo_path).await;
        let _ = tx.send(BackgroundEvent::LogsLoaded(result));
    });
}