1use std::io::Write;
2use std::sync::Arc;
3
4use anyhow::Result;
5use log::{debug, info};
6use ratatui::Frame;
7use ratatui::layout::Rect;
8use ratatui::widgets::Clear;
9use tokio::sync::mpsc;
10
11use super::{DynamicHandlers, InterruptHandlers, State};
12use crate::action::{Action, Exit};
13use crate::config::{CursorSetting, ExitConfig, PreviewSetting, Side};
14use crate::message::{Interrupt, Event, RenderCommand};
15use crate::tui::Tui;
16use crate::ui::{InputUI, PickerUI, PreviewUI, ResultsUI, UI};
17use crate::{MatchmakerError, PickerItem, Selection};
18
19pub async fn render_loop<'a, W: Write, T: PickerItem, S: Selection, C>(
20 mut ui: UI,
21 mut picker_ui: PickerUI<'a, T, S, C>,
22 mut preview_ui: Option<PreviewUI>,
23 mut tui: Tui<W>,
24 mut render_rx: mpsc::UnboundedReceiver<RenderCommand>,
25 controller_tx: mpsc::UnboundedSender<Event>,
26 context: Arc<C>,
27 dynamic_handlers: DynamicHandlers<T, S, C>,
28 exit_config: ExitConfig
29) -> Result<Vec<S>> {
30 let mut buffer = Vec::with_capacity(256);
31 let mut state: State<S, C> = State::new(context);
32 if let Some(ref preview_ui) = preview_ui
33 && !preview_ui.command().is_empty()
34 {
35 state.update_preview(preview_ui.command());
36 }
37
38 'rendering: while render_rx.recv_many(&mut buffer, 256).await > 0 {
39 let mut did_pause = false;
40 let mut did_exit = false;
41
42 for event in &buffer {
43 let mut interrupt = Interrupt::None;
44
45 let PickerUI {
46 input,
47 results,
48 worker,
49 selections,
50 ..
51 } = &mut picker_ui;
52
53 if !matches!(event, RenderCommand::Tick) {
54 info!("Recieved {event:?}");
55 }
56
57 match event {
58 RenderCommand::Input(c) => {
59 input.input.insert(input.cursor as usize, *c);
60 input.cursor += 1;
61 }
62 RenderCommand::Resize(area) => {
63 tui.terminal.resize(area.clone());
64 }
65 RenderCommand::Refresh => {
66 tui.terminal.resize(tui.area);
67 }
68 RenderCommand::Action(action) => {
69 match action {
70 Action::Select => {
71 if let Some(item) = worker.get_nth(results.index()) {
72 selections.sel(item);
73 }
74 }
75 Action::Deselect => {
76 if let Some(item) = worker.get_nth(results.index()) {
77 selections.desel(item);
78 }
79 }
80 Action::Toggle => {
81 if let Some(item) = worker.get_nth(results.index()) {
82 selections.toggle(item);
83 }
84 }
85 Action::CycleAll => {
86 selections.cycle_all_bg(worker.raw_results());
87 }
88 Action::Accept => {
89 if selections.is_empty()
90 && let Some(item) = worker.get_nth(results.index())
91 {
92 selections.sel(item);
93 }
94 return Ok(selections.output().collect::<Vec<S>>());
95 }
96 Action::Quit(code) => {
97 return Err(MatchmakerError::Abort(code.into()).into());
98 }
99
100 Action::ChangeHeader(context) => {
102 todo!()
103 }
104
105
106 Action::CyclePreview => {
107 if let Some(p) = preview_ui.as_mut() {
108 p.cycle_layout();
109 if !p.command().is_empty() {
110 state.update_preview(p.command().as_str());
111 }
112 }
113 }
114 Action::Preview(context) => {
115 if let Some(p) = preview_ui.as_mut() {
116 if !state.update_preview(context.as_str()) {
117 p.toggle_show()
118 } else {
119 p.show::<true>();
120 }
121 };
122 }
123 Action::SwitchPreview(idx) => {
124 if let Some(p) = preview_ui.as_mut() {
125 if let Some(idx) = idx {
126 if !p.set_idx(*idx) && !state.update_preview(p.command()) {
127 p.toggle_show();
128 }
129 } else {
130 p.toggle_show()
131 }
132 }
133 }
134 Action::SetPreview(idx) => {
135 if let Some(p) = preview_ui.as_mut()
136 {
137 if let Some(idx) = idx {
138 p.set_idx(*idx);
139 } else {
140 state.update_preview(p.command());
141 }
142 }
143 }
144
145 Action::Execute(context) => {
147 interrupt = Interrupt::Execute(context.into());
148 }
149 Action::Become(context) => {
150 interrupt = Interrupt::Become(context.into());
151 }
152 Action::Reload(context) => {
153 interrupt = Interrupt::Reload(context.into());
154 }
155 Action::Print(context) => {
156 interrupt = Interrupt::Print(context.into());
157 }
158
159
160 Action::SetInput(context) => {
161 input.set_input(context.into(), u16::MAX);
162 }
163 Action::Column(context) => {
164 results.toggle_col(*context);
165 }
166 Action::ForwardChar => input.forward_char(),
168 Action::BackwardChar => input.backward_char(),
169 Action::ForwardWord => input.forward_word(),
170 Action::BackwardWord => input.backward_word(),
171 Action::DeleteChar => input.delete(),
172 Action::DeleteWord => input.delete_word(),
173 Action::DeleteLineStart => input.delete_line_start(),
174 Action::DeleteLineEnd => input.delete_line_end(),
175 Action::Cancel => input.cancel(),
176
177 Action::Up(x) | Action::Down(x) => {
179 let next = matches!(action, Action::Down(_)) ^ results.reverse();
180 for _ in 0..x.into() {
181 if next {
182 results.cursor_next();
183 } else {
184 results.cursor_prev();
185 }
186 }
187 }
188 Action::PreviewUp(n) => {
189 preview_ui.as_mut().map(|p| p.up(n.into()));
190 }
191 Action::PreviewDown(n) => {
192 preview_ui.as_mut().map(|p| p.down(n.into()));
193 }
194 Action::PreviewHalfPageUp => todo!(),
195 Action::PreviewHalfPageDown => todo!(),
196 Action::Pos(pos) => {
197 let pos = if *pos >= 0 {
198 *pos as u32
199 } else {
200 results.status.matched_count.saturating_sub((-*pos) as u32)
201 };
202 results.cursor_jump(pos);
203 }
204
205 Action::Redraw => {
207 tui.terminal.resize(tui.area);
208 }
209 _ => {}
210 }
211 }
212 _ => {}
213 }
214
215 if !matches!(interrupt, Interrupt::None) {
216 match interrupt {
217 Interrupt::Execute(_) => {
218 controller_tx.send(Event::Pause);
219 did_exit = true;
220 tui.enter_execute();
221 did_pause = true;
222 while let Some(msg) = render_rx.recv().await {
223 if matches!(msg, RenderCommand::Ack) {
224 break
225 }
226 }
227 }
228 Interrupt::Reload(_) => {
229 picker_ui.worker.restart(false);
230 }
231 Interrupt::Become(_) => {
232 tui.exit();
233 }
234 _ => {}
235 }
236
237 state.update(&picker_ui);
238 let dispatcher = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
239
240 for h in dynamic_handlers.1.get(&interrupt) {
241 (h)(dispatcher.clone(), &interrupt);
242 };
243
244 match interrupt {
245 Interrupt::Become(context) => return Err(MatchmakerError::Become(context).into()),
246 _ => {}
247 }
248 };
249 }
250
251 debug!("{:?}", picker_ui.results.widths());
254
255 picker_ui.update();
257
258 if did_exit {
259 tui.return_execute();
260 }
261
262 let mut resized = false;
263 tui.terminal.draw(|frame| {
264 let area = frame.area();
265
266 let [preview, picker_area] = if let Some(preview_ui) = preview_ui.as_ref()
267 && preview_ui.is_show()
268 {
269 preview_ui.layout().split(area)
270 } else {
271 [Rect::default(), area]
272 };
273
274 let [input, status, results] = picker_ui.layout(picker_area);
275
276 resized = state.update_layout([preview, input, status, results]);
277
278 if resized {
280 picker_ui.results.update_dimensions(&results);
281 ui.update_dimensions(area);
282 };
283
284 render_input(frame, input, &picker_ui.input);
285 render_status(frame, status, &picker_ui.results);
286 render_results(frame, results, &mut picker_ui);
287
288 if let Some(preview_ui) = preview_ui.as_mut() {
289 state.update_preview_ui(&preview_ui);
290 if resized {
291 preview_ui.update_dimensions(&preview);
292 }
293 render_preview(frame, preview, preview_ui);
294 }
295 })?;
296 if resized {
297 tui.terminal.resize(tui.area);
298 }
299
300 if state.iterations == 0 {
301 state.insert(Event::Start);
302 }
303 state.update(&picker_ui);
304 let events = state.events();
305 let dispatcher = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
306
307 if exit_config.select_1 && dispatcher.status().matched_count == 1 {
308 return Ok(vec![state.current().unwrap()]);
309 }
310 for e in events.iter() {
313 for h in dynamic_handlers.0.get(e) {
314 (h)(dispatcher.clone(), e)
315 }
316 }
317 for e in events {
318 controller_tx
319 .send(e)
320 .unwrap_or_else(|err| eprintln!("send failed: {:?}", err));
321 }
322
323 buffer.clear();
324
325 if did_pause {
326 controller_tx.send(Event::Resume);
327 while let Some(msg) = render_rx.recv().await {
329 if matches!(msg, RenderCommand::Ack) {
330 break
331 }
332 }
333 }
334 }
335
336 Err(MatchmakerError::EventLoopClosed.into())
337}
338
339fn render_preview(frame: &mut Frame, area: Rect, ui: &mut PreviewUI) {
341 if ui.view.changed() {
343 frame.render_widget(Clear, area);
344 } else {
345 let widget = ui.make_preview();
346 frame.render_widget(widget, area);
347 }
348}
349
350fn render_results<T: PickerItem, S: Selection, C>(
351 frame: &mut Frame,
352 area: Rect,
353 ui: &mut PickerUI<T, S, C>,
354) {
355 let widget = ui.make_table();
356
357 frame.render_widget(widget, area);
358}
359
360fn render_input(frame: &mut Frame, area: Rect, ui: &InputUI) {
361 let widget = ui.make_input();
362 match ui.config.cursor {
363 CursorSetting::Default => frame.set_cursor_position(ui.cursor_offset(&area)),
364 _ => {}
365 };
366
367 frame.render_widget(widget, area);
368}
369
370fn render_status(frame: &mut Frame, area: Rect, ui: &ResultsUI) {
371 let widget = ui.make_status();
372
373 frame.render_widget(widget, area);
374}
375
376#[cfg(test)]
379mod test {}
380
381