lean-tui 1.1.1

Standalone TUI infoview for Lean 4 theorem prover
mod before_after;
mod compact_tree;
mod data_flow;
mod deduction_tree;
mod display_mode;
mod effect_flow;
mod error;
mod event_loop;
mod graph_layout;
mod keybindings;
mod message;
mod model;
mod navigation;
mod protocol;
#[cfg(test)]
mod scenario_replay;
mod selection;
mod tcp_client;
mod theme;
mod update;
mod view;
mod widgets;

use std::{
    fs,
    io::{stdout, Stdout},
    path::PathBuf,
    process,
};

use clap::Parser;
use crossterm::{
    event::{DisableMouseCapture, EnableMouseCapture},
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use ratatui::prelude::*;
use tokio::runtime::Builder as RuntimeBuilder;

pub use crate::keybindings::keybinding_reference_markdown;
use crate::{error::Result, event_loop::run_event_loop};

const SCANNED_PORT_COUNT: u16 = 10;

#[derive(Parser)]
#[command(version, about)]
pub struct Args {
    #[arg(short, long, help = "Enable verbose logging")]
    verbose: bool,

    #[arg(
        short,
        long,
        default_value_t = 9742,
        help = "First TCP port scanned for lean-prism workers"
    )]
    port: u16,
}

pub fn init_logging(verbose: bool) {
    let log_path = dirs::cache_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("lean-tui.log");

    if let Ok(log_file) = fs::File::create(&log_path) {
        let level = if verbose { "debug" } else { "info" };
        let filter = tracing_subscriber::EnvFilter::from_default_env()
            .add_directive(format!("lean_tui={level}").parse().unwrap_or_default());
        tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_writer(log_file)
            .with_ansi(true)
            .with_target(false)
            .pretty()
            .init();
    }
}

pub fn run_from_args() {
    let args = Args::parse();
    init_logging(args.verbose);

    if let Err(e) = run_blocking(args.port) {
        eprintln!("Error: {e}");
        process::exit(1);
    }
}

fn run_blocking(port: u16) -> Result<()> {
    RuntimeBuilder::new_multi_thread()
        .enable_all()
        .build()?
        .block_on(run(port))
}

async fn run(port: u16) -> Result<()> {
    let mut terminal = setup_terminal()?;
    let result = run_event_loop(
        &mut terminal,
        port..=port.saturating_add(SCANNED_PORT_COUNT),
    )
    .await;
    disable_raw_mode()?;
    stdout()
        .execute(DisableMouseCapture)?
        .execute(LeaveAlternateScreen)?;
    result
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
    enable_raw_mode()?;
    stdout()
        .execute(EnterAlternateScreen)?
        .execute(EnableMouseCapture)?;
    Ok(Terminal::new(CrosstermBackend::new(stdout()))?)
}