mod app;
mod constants;
mod curl;
mod discovery;
mod messages;
mod models;
mod network;
mod storage;
mod tui;
use crossterm::{
event::{self, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::prelude::*;
use std::io;
use std::time::Duration;
use tokio::sync::mpsc;
use app::AppActor;
use messages::ui_events::key_to_ui_event;
use messages::{NetworkCommand, NetworkResponse, RenderState, UiEvent};
use network::NetworkActor;
struct TerminalGuard;
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let file_appender = tracing_appender::rolling::never(".", "freeman.log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::fmt()
.with_writer(non_blocking)
.with_ansi(false)
.init();
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
default_hook(panic_info);
}));
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let _guard = TerminalGuard;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let (ui_tx, ui_rx) = mpsc::unbounded_channel::<UiEvent>();
let (net_cmd_tx, net_cmd_rx) = mpsc::unbounded_channel::<NetworkCommand>();
let (net_resp_tx, net_resp_rx) = mpsc::unbounded_channel::<NetworkResponse>();
let (render_tx, mut render_rx) = mpsc::unbounded_channel::<std::sync::Arc<RenderState>>();
let network_actor = NetworkActor::new(net_resp_tx);
tokio::spawn(network_actor.run(net_cmd_rx));
let app_actor = AppActor::new(net_cmd_tx, render_tx);
tokio::spawn(app_actor.run(ui_rx, net_resp_rx));
run_ui_loop(&mut terminal, ui_tx, &mut render_rx).await?;
Ok(())
}
async fn run_ui_loop(
terminal: &mut Terminal<impl Backend>,
ui_tx: mpsc::UnboundedSender<UiEvent>,
render_rx: &mut mpsc::UnboundedReceiver<std::sync::Arc<RenderState>>,
) -> anyhow::Result<()> {
let mut current_state = std::sync::Arc::new(RenderState::default());
loop {
terminal.draw(|f| tui::draw::draw_ui(f, ¤t_state))?;
if event::poll(Duration::from_millis(50))? {
if let Event::Key(key) = event::read()? {
if let Some(event) = key_to_ui_event(
key,
current_state.active_tab,
current_state.http.active_panel,
current_state.input_mode,
current_state.show_help,
current_state.http.show_curl_import,
current_state.http.show_workspace_input,
) {
if matches!(event, UiEvent::Quit) {
let _ = ui_tx.send(event);
break;
}
let _ = ui_tx.send(event);
}
}
}
while let Ok(state) = render_rx.try_recv() {
current_state = state;
}
}
Ok(())
}