use std::time::Duration;
use crossterm::event::{Event, EventStream, KeyEventKind};
use futures::StreamExt;
use tokio::sync::mpsc;
use tokio::time;
use crate::services::terminal::TerminalSession;
use crate::state::{AppState, Step};
use crate::ui;
const TICK: Duration = Duration::from_millis(200);
pub async fn run_tui() -> anyhow::Result<()> {
let mut terminal = TerminalSession::enter()?;
let (app_tx, mut app_rx) = mpsc::unbounded_channel();
let mut state = AppState::new(app_tx);
let mut event_stream = EventStream::new();
let mut tick_interval = time::interval(TICK);
loop {
terminal.draw(|frame| ui::render(frame, &mut state))?;
tokio::select! {
maybe_event = event_stream.next() => {
let Some(event_result) = maybe_event else { break };
let event = match event_result {
Ok(event) => event,
Err(e) => {
state.add_log(&format!("事件读取失败: {e}"));
continue;
}
};
let Event::Key(key) = event else { continue };
if key.kind == KeyEventKind::Release { continue }
if matches!(state.handle_key(key), Step::Exit) { break }
}
Some(app_event) = app_rx.recv() => {
state.handle_app_event(app_event);
}
_ = tick_interval.tick() => {
state.on_tick();
}
}
}
if let Some(handle) = state.ctx.startup_handle.take() {
handle.abort();
}
if let Some(handle) = state.ctx.event_forwarder.take() {
handle.abort();
}
if let Some(tunnel) = state.ctx.tunnel.take() {
let _ = tokio::time::timeout(Duration::from_secs(3), tunnel.close()).await;
}
Ok(())
}