use crate::app::App;
use crate::app::message::Message;
use crate::command::Command;
use crate::error::AppError;
use crate::renderer::Renderer;
use crate::session::terminal::{TerminalSession, inline_viewport_height};
use acp_utils::client::{AcpClientHandle, AcpEvent};
use crossterm::event::{Event, EventStream};
use crossterm::terminal::size;
use futures::StreamExt;
use ratatui::Viewport;
use std::collections::VecDeque;
use std::future::pending;
use std::time::{Duration, Instant};
use tokio::select;
use tokio::sync::mpsc;
use tokio::time::{MissedTickBehavior, interval};
use super::CommandDispatcher;
const MAX_ACP_EVENTS_PER_FRAME: usize = 1_000;
pub async fn run(
mut app: App,
mut renderer: Renderer,
mut event_rx: mpsc::UnboundedReceiver<AcpEvent>,
client_handle: AcpClientHandle,
) -> Result<(), AppError> {
let mut dispatcher = CommandDispatcher::new(client_handle);
let result = async {
let (_, terminal_height) = size()?;
let viewport = Viewport::Inline(inline_viewport_height(terminal_height));
let mut session = TerminalSession::enter(viewport)?;
let initial_commands = app.take_commands();
dispatch_commands(&mut dispatcher, &mut app, initial_commands);
run_event_loop(&mut session, &mut app, &mut renderer, &mut event_rx, &mut dispatcher).await
}.await;
dispatcher.shutdown().await;
result
}
async fn run_event_loop(
session: &mut TerminalSession,
app: &mut App,
renderer: &mut Renderer,
event_rx: &mut mpsc::UnboundedReceiver<AcpEvent>,
dispatcher: &mut CommandDispatcher,
) -> Result<(), AppError> {
let mut terminal_events = EventStream::new();
let mut tick_interval = {
let mut tick = interval(Duration::from_millis(100));
tick.set_missed_tick_behavior(MissedTickBehavior::Skip);
tick
};
renderer.draw(session.terminal_mut(), app)?;
loop {
let tick_fut = async {
if !app.wants_tick() {
pending::<()>().await;
}
tick_interval.tick().await;
};
let has_pending_tasks = dispatcher.has_pending_tasks();
select! {
terminal_event = terminal_events.next() => {
match terminal_event {
Some(Ok(event)) => {
if matches!(event, Event::Resize(..)) {
session.resync_inline_viewport()?;
}
let commands = app.update(Message::Terminal(event));
dispatch_commands(dispatcher, app, commands);
}
Some(Err(error)) => return Err(AppError::Io(error)),
None => return Ok(()),
}
}
acp_event = event_rx.recv() => {
let event = acp_event.unwrap_or(AcpEvent::ConnectionClosed);
let commands = app.update(Message::Agent(Box::new(event)));
dispatch_commands(dispatcher, app, commands);
for _ in 1..MAX_ACP_EVENTS_PER_FRAME {
let Ok(event) = event_rx.try_recv() else { break };
let commands = app.update(Message::Agent(Box::new(event)));
dispatch_commands(dispatcher, app, commands);
}
}
result = dispatcher.next_result(), if has_pending_tasks => {
if let Some(result) = result {
let commands = app.update(Message::CommandFinished(Box::new(result)));
dispatch_commands(dispatcher, app, commands);
}
}
() = tick_fut => {
let commands = app.update(Message::Tick(Instant::now()));
dispatch_commands(dispatcher, app, commands);
},
}
session.set_mouse_capture(app.needs_mouse_capture());
if let Some(result) = app.exit_result() {
return result;
}
renderer.draw(session.terminal_mut(), app)?;
}
}
fn dispatch_commands(dispatcher: &mut CommandDispatcher, app: &mut App, commands: Vec<Command>) {
let mut pending = VecDeque::from(commands);
while let Some(command) = pending.pop_front() {
let Some(result) = dispatcher.dispatch(command) else { continue };
pending.extend(app.update(Message::CommandFinished(Box::new(result))));
}
}