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::layout::{Rect, Size};
11use typed_builder::TypedBuilder;
12
13use super::{TerminalGuard, app::App, event::RuntimeEvent, watch::NotifyConfigWatcher};
14use crate::{
15 application::Workspace,
16 domain::port::{Notifier, PathCompleter, ProcessRunner, ProjectRegistry, SettingsStore},
17 error::Result,
18};
19
20#[derive(TypedBuilder)]
25pub struct Adapters {
26 runner: Box<dyn ProcessRunner>,
27 registry: Box<dyn ProjectRegistry>,
28 completer: Box<dyn PathCompleter + Send>,
29 notifier: Box<dyn Notifier>,
30 settings_store: Box<dyn SettingsStore>,
31}
32
33const INPUT_POLL_INTERVAL: Duration = Duration::from_millis(50);
35const OUTPUT_CAPACITY: usize = 1024;
38const MAX_BATCH: usize = 512;
40
41pub fn run(
48 guard: &mut TerminalGuard,
49 workspace: Workspace,
50 adapters: Adapters,
51 current_config: PathBuf,
52) -> Result<()> {
53 let Adapters {
54 runner,
55 registry,
56 completer,
57 notifier,
58 settings_store,
59 } = adapters;
60 let (control_tx, control_rx) = unbounded();
61 let (output_tx, output_rx) = bounded(OUTPUT_CAPACITY);
62 let watch_tx = output_tx.clone();
63 spawn_input_thread(control_tx);
64
65 let area = size_to_rect(guard.terminal_mut().size()?);
66 let mut app = App::new(
67 workspace,
68 runner,
69 output_tx,
70 area,
71 completer,
72 registry,
73 current_config,
74 );
75 app.spawn_completion_worker();
76 app.set_config_watcher(Box::new(NotifyConfigWatcher::new(watch_tx)));
77 app.set_notifier(notifier);
78 app.set_settings_store(settings_store);
79 app.start();
80
81 let result = run_loop(guard, &mut app, &control_rx, &output_rx);
84 app.shutdown();
85 result
86}
87
88fn run_loop(
93 guard: &mut TerminalGuard,
94 app: &mut App,
95 control_rx: &Receiver<RuntimeEvent>,
96 output_rx: &Receiver<RuntimeEvent>,
97) -> Result<()> {
98 guard.terminal_mut().draw(|frame| app.render(frame))?;
99 while app.is_running() {
100 match drain(app, control_rx, output_rx) {
101 ControlFlow::Break(()) => break,
102 ControlFlow::Continue(redraw) if redraw && app.is_running() => {
103 guard.terminal_mut().draw(|frame| app.render(frame))?;
104 },
105 ControlFlow::Continue(_) => {},
106 }
107 }
108 Ok(())
109}
110
111fn drain(
115 app: &mut App,
116 control_rx: &Receiver<RuntimeEvent>,
117 output_rx: &Receiver<RuntimeEvent>,
118) -> ControlFlow<(), bool> {
119 let activity_timeout = app
120 .next_activity_deadline()
121 .map(|deadline| after(deadline.saturating_duration_since(Instant::now())))
122 .unwrap_or_else(never);
123 let mut redraw = false;
124 select! {
125 recv(control_rx) -> msg => match msg {
126 Ok(event) => if !apply(app, event) {
127 return ControlFlow::Break(());
128 } else {
129 redraw = true;
130 },
131 Err(_) => return ControlFlow::Break(()),
132 },
133 recv(output_rx) -> msg => if let Ok(event) = msg {
134 if !apply(app, event) {
135 return ControlFlow::Break(());
136 }
137 redraw = true;
138 },
139 recv(activity_timeout) -> now => if let Ok(now) = now {
140 redraw = app.expire_quiet_activity(now);
141 },
142 }
143 while let Ok(event) = control_rx.try_recv() {
144 if !apply(app, event) {
145 return ControlFlow::Break(());
146 }
147 redraw = true;
148 }
149 for _ in 0..MAX_BATCH {
150 match output_rx.try_recv() {
151 Ok(event) => {
152 if !apply(app, event) {
153 return ControlFlow::Break(());
154 }
155 redraw = true;
156 },
157 Err(_) => break,
158 }
159 }
160 ControlFlow::Continue(redraw)
161}
162
163fn apply(app: &mut App, event: RuntimeEvent) -> bool {
165 match event {
166 RuntimeEvent::Input(event) => app.handle_input(event),
167 RuntimeEvent::Output {
168 pane,
169 generation,
170 output,
171 } => app.handle_output(pane, generation, output),
172 RuntimeEvent::Respawn { pane, generation } => app.handle_respawn(pane, generation),
173 RuntimeEvent::ForceStop {
174 pane,
175 spawn_generation,
176 shutdown_generation,
177 } => app.handle_force_stop(pane, spawn_generation, shutdown_generation),
178 RuntimeEvent::Completions {
179 generation,
180 candidates,
181 } => app.handle_completions(generation, candidates),
182 RuntimeEvent::ConfigChanged { path } => app.handle_config_changed(path),
183 RuntimeEvent::InputClosed => return false,
184 }
185 true
186}
187
188fn spawn_input_thread(sender: Sender<RuntimeEvent>) {
191 thread::spawn(move || {
192 loop {
193 match event::poll(INPUT_POLL_INTERVAL) {
194 Ok(true) => match event::read() {
195 Ok(event) => {
196 if sender.send(RuntimeEvent::Input(event)).is_err() {
197 break;
198 }
199 },
200 Err(_) => {
201 let _ = sender.send(RuntimeEvent::InputClosed);
202 break;
203 },
204 },
205 Ok(false) => {},
206 Err(_) => {
207 let _ = sender.send(RuntimeEvent::InputClosed);
208 break;
209 },
210 }
211 }
212 });
213}
214
215fn size_to_rect(size: Size) -> Rect {
217 Rect::new(0, 0, size.width, size.height)
218}