1mod dynamic;
2mod state;
3mod state_effects;
4
5pub use dynamic::*;
6pub use state::*;
7pub use state_effects::*;
8use std::io::Write;
11
12use anyhow::Result;
13use log::{info, warn};
14use ratatui::Frame;
15use ratatui::layout::Rect;
16use tokio::sync::mpsc;
17
18use crate::action::{Action, ActionExt, ActionAliaser, ActionExtHandler};
19use crate::config::{CursorSetting, ExitConfig};
20use crate::message::{Event, Interrupt, RenderCommand};
21use crate::tui::Tui;
22use crate::ui::{DisplayUI, InputUI, OverlayUI, PickerUI, PreviewUI, ResultsUI, UI};
23use crate::{MatchError, SSS, Selection};
24
25fn apply_aliases<T: SSS, S: Selection, A: ActionExt>(
27 buffer: &mut Vec<RenderCommand<A>>,
28 aliaser: ActionAliaser<T, S, A>,
29 state: &MMState<'_, T, S>
30) {
31 let mut out = Vec::new();
32
33 for cmd in buffer.drain(..) {
34 match cmd {
35 RenderCommand::Action(a) => {
36 out.extend(aliaser(a, state).0.into_iter().map(RenderCommand::Action))
37 },
38 other => out.push(other),
39 }
40 }
41
42 *buffer = out;
43}
44
45#[allow(clippy::too_many_arguments)]
46pub(crate) async fn render_loop<'a, W: Write, T: SSS, S: Selection, A: ActionExt>(
47 mut ui: UI,
48 mut picker_ui: PickerUI<'a, T, S>,
49 mut preview_ui: Option<PreviewUI>,
50 mut tui: Tui<W>,
51
52 mut overlay_ui: Option<OverlayUI<A>>,
53 exit_config: ExitConfig,
54
55 mut render_rx: mpsc::UnboundedReceiver<RenderCommand<A>>,
56 controller_tx: mpsc::UnboundedSender<Event>,
57
58 dynamic_handlers: DynamicHandlers<T, S>,
59 ext_handler: Option<ActionExtHandler<T, S, A>>,
60 ext_aliaser: Option<ActionAliaser<T, S, A>>,
61 initializer: Option<Box<dyn FnOnce()>>
62 ) -> Result<Vec<S>, MatchError> {
64 if let Some(initializer) = initializer {
65 initializer()
66 }
67 let mut buffer = Vec::with_capacity(256);
68
69 let mut state: State<S> = State::new();
70
71 if let Some(ref preview_ui) = preview_ui
73 && !preview_ui.command().is_empty()
74 {
75 state.update_preview(preview_ui.command());
76 }
77
78 while render_rx.recv_many(&mut buffer, 256).await > 0 {
79 let mut did_pause = false;
80 let mut did_exit = false;
81 let mut did_resize = false;
82
83 let mut effects = Effects::new();
84 if let Some(aliaser) = ext_aliaser {
86 let state = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
87 apply_aliases(&mut buffer, aliaser, &state)
88 };
90
91 for event in buffer.drain(..) {
93 let mut interrupt = Interrupt::None;
94
95 let PickerUI {
96 input,
97 results,
98 worker,
99 selections,
100 header,
101 footer,
102 ..
103 } = &mut picker_ui;
104
105 if !matches!(event, RenderCommand::Tick) {
106 info!("Recieved {event:?}");
107 }
108
109 match event {
110 RenderCommand::Input(c) => {
111 if let Some(x) = overlay_ui.as_mut() && x.handle_input(c) {
112 continue;
113 }
114 input.input.insert(input.cursor as usize, c);
115 input.cursor += 1;
116 }
117 RenderCommand::Resize(area) => {
118 tui.resize(area);
119 ui.area = area;
120 }
121 RenderCommand::Refresh => {
122 tui.redraw();
123 }
124 RenderCommand::Effect(e) => {
125 #[allow(warnings)]
126 match e {
127 Effect::Reload => {
128 interrupt = Interrupt::Reload("".into());
131 }
132 _ => {
133 effects.insert(e);
134 }
135 }
136
137 }
138 RenderCommand::Action(action) => {
139 if let Some(x) = overlay_ui.as_mut() && x.handle_action(&action) {
140 continue;
141 }
142 match action {
143 Action::Select => {
144 if let Some(item) = worker.get_nth(results.index()) {
145 selections.sel(item);
146 }
147 }
148 Action::Deselect => {
149 if let Some(item) = worker.get_nth(results.index()) {
150 selections.desel(item);
151 }
152 }
153 Action::Toggle => {
154 if let Some(item) = worker.get_nth(results.index()) {
155 selections.toggle(item);
156 }
157 }
158 Action::CycleAll => {
159 selections.cycle_all_bg(worker.raw_results());
160 }
161 Action::ClearAll => {
162 selections.clear();
163 }
164 Action::Accept => {
165 if selections.is_empty() {
166 if let Some(item) = worker.get_nth(results.index())
167 {
168 selections.sel(item);
169 } else if !exit_config.allow_empty {
170 continue;
171 }
172 }
173 return Ok(selections.output().collect::<Vec<S>>());
174 }
175 Action::Quit(code) => {
176 return Err(MatchError::Abort(code.0));
177 }
178
179 Action::SetHeader(context) => {
181 if let Some(s) = context {
182 header.set(s);
183 } else {
184 todo!()
185 }
186 }
187 Action::SetFooter(context) => {
188 if let Some(s) = context {
189 footer.set(s);
190 } else {
191 todo!()
192 }
193 }
194 Action::CyclePreview => {
196 if let Some(p) = preview_ui.as_mut() {
197 p.cycle_layout();
198 if !p.command().is_empty() {
199 state.update_preview(p.command());
200 }
201 }
202 }
203 Action::Preview(context) => {
204 if let Some(p) = preview_ui.as_mut() {
205 if !state.update_preview(context.as_str()) {
206 p.toggle_show()
207 } else {
208 p.show::<true>();
209 }
210 };
211 }
212 Action::Help(context) => {
213 if let Some(p) = preview_ui.as_mut() {
214 if !state.update_preview_set(context) {
216 state.update_preview_unset()
217 } else {
218 p.show::<true>();
219 }
220 };
221 }
222 Action::SwitchPreview(idx) => {
223 if let Some(p) = preview_ui.as_mut() {
224 if let Some(idx) = idx {
225 if !p.set_idx(idx) && !state.update_preview(p.command()) {
226 p.toggle_show();
227 }
228 } else {
229 p.toggle_show()
230 }
231 }
232 }
233 Action::SetPreview(idx) => {
234 if let Some(p) = preview_ui.as_mut() {
235 if let Some(idx) = idx {
236 p.set_idx(idx);
237 } else {
238 state.update_preview(p.command());
239 }
240 }
241 }
242 Action::ToggleWrap => {
243 results.wrap(!results.is_wrap());
244 }
245 Action::ToggleWrapPreview => {
246 if let Some(p) = preview_ui.as_mut() {
247 p.wrap(!p.is_wrap());
248 }
249 }
250
251 Action::Execute(context) => {
253 interrupt = Interrupt::Execute(context);
254 }
255 Action::Become(context) => {
256 interrupt = Interrupt::Become(context);
257 }
258 Action::Reload(context) => {
259 interrupt = Interrupt::Reload(context);
260 }
261 Action::Print(context) => {
262 interrupt = Interrupt::Print(context);
263 }
264
265 Action::SetInput(context) => {
266 input.set(context, u16::MAX);
267 }
268 Action::Column(context) => {
269 results.toggle_col(context);
270 }
271 Action::CycleColumn => {
272 results.cycle_col();
273 }
274 Action::ForwardChar => input.forward_char(),
276 Action::BackwardChar => input.backward_char(),
277 Action::ForwardWord => input.forward_word(),
278 Action::BackwardWord => input.backward_word(),
279 Action::DeleteChar => input.delete(),
280 Action::DeleteWord => input.delete_word(),
281 Action::DeleteLineStart => input.delete_line_start(),
282 Action::DeleteLineEnd => input.delete_line_end(),
283 Action::Cancel => input.cancel(),
284
285 Action::Up(x) | Action::Down(x) => {
287 let next = matches!(action, Action::Down(_)) ^ results.reverse();
288 for _ in 0..x.into() {
289 if next {
290 results.cursor_next();
291 } else {
292 results.cursor_prev();
293 }
294 }
295 }
296 Action::PreviewUp(n) => {
297 if let Some(p) = preview_ui.as_mut() {
298 p.up(n.into())
299 }
300 }
301 Action::PreviewDown(n) => {
302 if let Some(p) = preview_ui.as_mut() {
303 p.down(n.into())
304 }
305 }
306 Action::PreviewHalfPageUp => todo!(),
307 Action::PreviewHalfPageDown => todo!(),
308 Action::Pos(pos) => {
309 let pos = if pos >= 0 {
310 pos as u32
311 } else {
312 results.status.matched_count.saturating_sub((-pos) as u32)
313 };
314 results.cursor_jump(pos);
315 }
316 Action::InputPos(pos) => {
317 let pos = if pos >= 0 {
318 pos as u16
319 } else {
320 (input.len() as u16).saturating_sub((-pos) as u16)
321 };
322 input.cursor = pos;
323 }
324
325 Action::Redraw => {
327 tui.redraw();
328 }
329 Action::Overlay(index) => {
330 if let Some(x) = overlay_ui.as_mut() {
331 x.enable(index, &ui.area);
332 tui.redraw();
333 };
334 }
335 Action::Custom(e) => {
336 if let Some(handler) = ext_handler {
337 let mut dispatcher = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
338 let effects = handler(e, &mut dispatcher);
339 state.apply_effects(effects, &mut ui, &mut picker_ui, &mut preview_ui);
340 }
341 }
342 _ => {}
343 }
344 }
345 _ => {}
346 }
347
348 match interrupt {
349 Interrupt::None => continue,
350 Interrupt::Execute(_) => {
351 if controller_tx.send(Event::Pause).is_err() {
352 break
353 }
354 did_exit = true;
355 tui.enter_execute();
356 did_pause = true;
357 }
358 Interrupt::Reload(_) => {
359 picker_ui.worker.restart(false);
360 }
361 Interrupt::Become(_) => {
362 tui.exit();
363 }
364 _ => {}
365 }
366
367 state.update_current(&picker_ui);
368 {
370 let mut effects = Effects::new();
371 let mut dispatcher = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
372 for h in dynamic_handlers.1.get(&interrupt) {
373 effects.append(
374 h(&mut dispatcher, &interrupt)
375 )
376 }
377
378 if let Interrupt::Become(context) = interrupt {
379 return Err(MatchError::Become(context));
380 }
381 state.apply_effects(effects, &mut ui, &mut picker_ui, &mut preview_ui);
382 }
383 }
384
385 picker_ui.update();
389 if exit_config.select_1 && picker_ui.results.status.matched_count == 1 {
391 return Ok(vec![state.take_current().unwrap()]);
392 }
393
394 if did_exit {
396 tui.return_execute().map_err(|e| MatchError::TUIError(e.to_string()))?
397 }
398
399 let mut overlay_ui_ref = overlay_ui.as_mut();
400 tui.terminal
401 .draw(|frame| {
402 let mut area = frame.area();
403
404 render_ui(frame, &mut area, &ui);
405
406 let [preview, picker_area] = if let Some(preview_ui) = preview_ui.as_mut()
407 && let Some(layout) = preview_ui.layout()
408 {
409 let ret = layout.split(area);
410 if state.iterations == 0 && ret[1].width <= 5 {
411 warn!("UI too narrow, hiding preview");
412 preview_ui.show::<false>();
413 [Rect::default(), area]
414 } else {
415 ret
416 }
417 } else {
418 [Rect::default(), area]
419 };
420
421 let [input, status, header, results, footer] = picker_ui.layout(picker_area);
422
423 did_resize = state.update_layout([preview, input, status, results]);
425
426 if did_resize {
427 picker_ui.results.update_dimensions(&results);
428 ui.update_dimensions(area);
430 if let Some(x) = overlay_ui_ref.as_deref_mut() {
431 x.update_dimensions(&area);
432 }
433 };
434
435 render_input(frame, input, &picker_ui.input);
436 render_status(frame, status, &picker_ui.results);
437 render_results(frame, results, &mut picker_ui);
438 render_display(frame, header, &picker_ui.header, picker_ui.results.indentation());
439 render_display(frame, footer, &picker_ui.footer, picker_ui.results.indentation());
440 if let Some(preview_ui) = preview_ui.as_mut() {
441 state.update_preview_ui(preview_ui);
442 if did_resize {
443 preview_ui.update_dimensions(&preview);
444 }
445 render_preview(frame, preview, preview_ui);
446 }
447 if let Some(x) = overlay_ui_ref {
448 x.draw(frame);
449 }
450 })
451 .map_err(|e| MatchError::TUIError(e.to_string()))?;
452
453 if did_resize && tui.config.redraw_on_resize {
455 tui.redraw();
456 }
457 buffer.clear();
458
459 state.update(&picker_ui, &overlay_ui);
462 let events = state.events();
463
464 let mut dispatcher = state.dispatcher(&ui, &picker_ui, preview_ui.as_ref());
466 for e in events.iter() {
476 for h in dynamic_handlers.0.get(e) {
477 effects.append(
478 h(&mut dispatcher, e)
479 )
480 }
481 }
482
483 state.apply_effects(effects, &mut ui, &mut picker_ui, &mut preview_ui);
485
486 for e in events {
489 controller_tx
490 .send(e)
491 .unwrap_or_else(|err| eprintln!("send failed: {:?}", err));
492 }
493 if did_pause {
496 if controller_tx.send(Event::Resume).is_err() {
497 break
498 };
499 while let Some(msg) = render_rx.recv().await {
501 if matches!(msg, RenderCommand::Ack) {
502 break;
503 }
504 }
505 }
506 }
507
508 Err(MatchError::EventLoopClosed)
509}
510
511fn render_preview(frame: &mut Frame, area: Rect, ui: &mut PreviewUI) {
513 let widget = ui.make_preview();
521 frame.render_widget(widget, area);
522}
523
524fn render_results<T: SSS, S: Selection>(
525 frame: &mut Frame,
526 area: Rect,
527 ui: &mut PickerUI<T, S>,
528) {
529 let widget = ui.make_table();
530
531 frame.render_widget(widget, area);
532}
533
534fn render_input(frame: &mut Frame, area: Rect, ui: &InputUI) {
535 let widget = ui.make_input();
536 if let CursorSetting::Default = ui.config.cursor {
537 frame.set_cursor_position(ui.cursor_offset(&area))
538 };
539
540 frame.render_widget(widget, area);
541}
542
543fn render_status(frame: &mut Frame, area: Rect, ui: &ResultsUI) {
544 let widget = ui.make_status();
545
546 frame.render_widget(widget, area);
547}
548
549fn render_display(frame: &mut Frame, area: Rect, ui: &DisplayUI, result_indentation: usize) {
550 let widget = ui.make_display(result_indentation);
551
552 frame.render_widget(widget, area);
553}
554
555fn render_ui(frame: &mut Frame, area: &mut Rect, ui: &UI) {
556 let widget = ui.make_ui();
557 frame.render_widget(widget, *area);
558 *area = ui.inner_area(area);
559}
560
561#[cfg(test)]
564mod test {}
565
566