1use std::{
2 ops::ControlFlow,
3 path::PathBuf,
4 thread,
5 time::{Duration, Instant},
6};
7
8use crossbeam_channel::{Receiver, Sender, after, bounded, never, select, unbounded};
9use crossterm::event;
10use ratatui::{
11 layout::{Rect, Size},
12 style::Style,
13};
14use typed_builder::TypedBuilder;
15
16use super::{TerminalGuard, app::App, event::RuntimeEvent, watch::NotifyConfigWatcher};
17use crate::{
18 application::Workspace,
19 domain::port::{
20 AgentSessionStore, Notifier, PathCompleter, ProcessRunner, ProjectRegistry, SettingsStore,
21 },
22 error::Result,
23};
24
25#[derive(TypedBuilder)]
30pub struct Adapters {
31 runner: Box<dyn ProcessRunner>,
32 registry: Box<dyn ProjectRegistry>,
33 completer: Box<dyn PathCompleter + Send>,
34 notifier: Box<dyn Notifier>,
35 settings_store: Box<dyn SettingsStore>,
36 agent_session_store: Box<dyn AgentSessionStore>,
37}
38
39const INPUT_POLL_INTERVAL: Duration = Duration::from_millis(50);
41const OUTPUT_CAPACITY: usize = 1024;
44const MAX_BATCH: usize = 512;
46
47pub fn run(
54 guard: &mut TerminalGuard,
55 workspace: Workspace,
56 adapters: Adapters,
57 current_config: PathBuf,
58 selection_style: Style,
59) -> Result<()> {
60 let Adapters {
61 runner,
62 registry,
63 completer,
64 notifier,
65 settings_store,
66 agent_session_store,
67 } = adapters;
68 let (control_tx, control_rx) = unbounded();
69 let (output_tx, output_rx) = bounded(OUTPUT_CAPACITY);
70 let watch_tx = output_tx.clone();
71 spawn_input_thread(control_tx);
72
73 let area = size_to_rect(guard.terminal_mut().size()?);
74 let mut app = App::new(
75 workspace,
76 runner,
77 output_tx,
78 area,
79 completer,
80 registry,
81 current_config,
82 );
83 app.spawn_completion_worker();
84 app.set_config_watcher(Box::new(NotifyConfigWatcher::new(watch_tx)));
85 app.set_notifier(notifier);
86 app.set_settings_store(settings_store);
87 app.set_agent_session_store(agent_session_store);
88 app.set_selection_style(selection_style);
89 app.start();
90
91 let result = run_loop(guard, &mut app, &control_rx, &output_rx);
94 app.shutdown();
95 result
96}
97
98fn run_loop(
103 guard: &mut TerminalGuard,
104 app: &mut App,
105 control_rx: &Receiver<RuntimeEvent>,
106 output_rx: &Receiver<RuntimeEvent>,
107) -> Result<()> {
108 guard.terminal_mut().draw(|frame| app.render(frame))?;
109 while app.is_running() {
110 let outcome = drain(app, control_rx, output_rx);
111 if let Some(text) = app.take_pending_clipboard() {
112 guard.copy_to_clipboard(&text)?;
113 }
114 if let Some(shape) = app.take_pending_pointer_shape() {
115 guard.set_pointer_shape(shape)?;
116 }
117 match outcome {
118 ControlFlow::Break(()) => break,
119 ControlFlow::Continue(redraw) if redraw && app.is_running() => {
120 app.refresh_selection_view();
121 guard.terminal_mut().draw(|frame| app.render(frame))?;
122 },
123 ControlFlow::Continue(_) => {},
124 }
125 }
126 Ok(())
127}
128
129fn drain(
133 app: &mut App,
134 control_rx: &Receiver<RuntimeEvent>,
135 output_rx: &Receiver<RuntimeEvent>,
136) -> ControlFlow<(), bool> {
137 let activity_timeout = app
138 .next_activity_deadline()
139 .map(|deadline| after(deadline.saturating_duration_since(Instant::now())))
140 .unwrap_or_else(never);
141 let activity_frame_timeout = app
142 .next_activity_frame_deadline()
143 .map(|deadline| after(deadline.saturating_duration_since(Instant::now())))
144 .unwrap_or_else(never);
145 let selection_timeout = app
146 .next_selection_deadline()
147 .map(|deadline| after(deadline.saturating_duration_since(Instant::now())))
148 .unwrap_or_else(never);
149 let mut redraw = false;
150 select! {
151 recv(control_rx) -> msg => match msg {
152 Ok(event) => if !apply(app, event) {
153 return ControlFlow::Break(());
154 } else {
155 redraw = true;
156 },
157 Err(_) => return ControlFlow::Break(()),
158 },
159 recv(output_rx) -> msg => if let Ok(event) = msg {
160 if !apply(app, event) {
161 return ControlFlow::Break(());
162 }
163 redraw = true;
164 },
165 recv(activity_timeout) -> now => if let Ok(now) = now {
166 redraw = app.expire_quiet_activity(now);
167 },
168 recv(activity_frame_timeout) -> now => if let Ok(now) = now {
169 redraw = app.advance_activity_frame(now);
170 },
171 recv(selection_timeout) -> now => if let Ok(now) = now {
172 redraw = app.advance_selection(now);
173 },
174 }
175 while let Ok(event) = control_rx.try_recv() {
176 if !apply(app, event) {
177 return ControlFlow::Break(());
178 }
179 redraw = true;
180 }
181 for _ in 0..MAX_BATCH {
182 match output_rx.try_recv() {
183 Ok(event) => {
184 if !apply(app, event) {
185 return ControlFlow::Break(());
186 }
187 redraw = true;
188 },
189 Err(_) => break,
190 }
191 }
192 let now = Instant::now();
193 redraw |= app.advance_activity_frame(now);
194 redraw |= app.advance_selection(now);
195 ControlFlow::Continue(redraw)
196}
197
198fn apply(app: &mut App, event: RuntimeEvent) -> bool {
200 match event {
201 RuntimeEvent::Input(event) => app.handle_input(event),
202 RuntimeEvent::Output {
203 pane,
204 generation,
205 output,
206 } => app.handle_output(pane, generation, output),
207 RuntimeEvent::Respawn { pane, generation } => app.handle_respawn(pane, generation),
208 RuntimeEvent::ForceStop {
209 pane,
210 spawn_generation,
211 shutdown_generation,
212 } => app.handle_force_stop(pane, spawn_generation, shutdown_generation),
213 RuntimeEvent::Completions {
214 generation,
215 candidates,
216 } => app.handle_completions(generation, candidates),
217 RuntimeEvent::ConfigChanged { path } => app.handle_config_changed(path),
218 RuntimeEvent::InputClosed => return false,
219 }
220 true
221}
222
223fn spawn_input_thread(sender: Sender<RuntimeEvent>) {
226 thread::spawn(move || {
227 loop {
228 match event::poll(INPUT_POLL_INTERVAL) {
229 Ok(true) => match event::read() {
230 Ok(event) => {
231 if sender.send(RuntimeEvent::Input(event)).is_err() {
232 break;
233 }
234 },
235 Err(_) => {
236 let _ = sender.send(RuntimeEvent::InputClosed);
237 break;
238 },
239 },
240 Ok(false) => {},
241 Err(_) => {
242 let _ = sender.send(RuntimeEvent::InputClosed);
243 break;
244 },
245 }
246 }
247 });
248}
249
250fn size_to_rect(size: Size) -> Rect {
252 Rect::new(0, 0, size.width, size.height)
253}