1use std::{
2 fmt::{self, Debug, Formatter},
3 process::{Command, Stdio},
4 sync::Arc,
5};
6
7use arrayvec::ArrayVec;
8use cli_boilerplate_automation::{_log, bath::PathExt, broc::CommandExt, env_vars};
9use easy_ext::ext;
10use log::{debug, info, warn};
11use ratatui::text::Text;
12
13use crate::{
14 MatchError, RenderFn, Result, SSS, Selection, Selector,
15 action::{Action, ActionExt, Actions, NullActionExt},
16 binds::BindMap,
17 config::{
18 ExitConfig, OverlayConfig, PreviewerConfig, RenderConfig, Split, TerminalConfig,
19 WorkerConfig,
20 },
21 event::{EventLoop, RenderSender},
22 message::{Event, Interrupt},
23 nucleo::{
24 Indexed, Segmented, Worker,
25 injector::{
26 AnsiInjector, Either, IndexedInjector, Injector, PreprocessOptions, SegmentedInjector,
27 SplitterFn, WorkerInjector,
28 },
29 },
30 preview::{
31 AppendOnly, Preview,
32 previewer::{PreviewMessage, Previewer},
33 },
34 render::{self, BoxedHandler, DynamicMethod, EventHandlers, InterruptHandlers, MMState},
35 tui,
36 ui::{Overlay, OverlayUI, UI},
37};
38
39pub struct Matchmaker<T: SSS, S: Selection = T> {
47 pub worker: Worker<T>,
48 pub render_config: RenderConfig,
49 pub tui_config: TerminalConfig,
50 pub exit_config: ExitConfig,
51 pub selector: Selector<T, S>,
52 pub event_handlers: EventHandlers<T, S>,
53 pub interrupt_handlers: InterruptHandlers<T, S>,
54}
55
56pub struct OddEnds {
60 pub formatter: Arc<RenderFn<ConfigMMItem>>,
61 pub splitter: SplitterFn<Either<String, Text<'static>>>,
62 pub hidden_columns: Vec<bool>
63}
64
65pub type ConfigInjector = AnsiInjector<
66SegmentedInjector<
67Either<String, Text<'static>>,
68IndexedInjector<Segmented<Either<String, Text<'static>>>, WorkerInjector<ConfigMMItem>>,
69>,
70>;
71pub type ConfigMatchmaker = Matchmaker<ConfigMMItem, Segmented<Either<String, Text<'static>>>>;
72pub type ConfigMMInnerItem = Segmented<Either<String, Text<'static>>>;
73pub type ConfigMMItem = Indexed<ConfigMMInnerItem>;
74
75impl ConfigMatchmaker {
76 pub fn new_from_config(
78 render_config: RenderConfig,
79 tui_config: TerminalConfig,
80 worker_config: WorkerConfig,
81 exit_config: ExitConfig,
82 preprocess_config: PreprocessOptions,
83 ) -> (Self, ConfigInjector, OddEnds) {
84 let cc = worker_config.columns;
85 let hidden_columns = cc.names.iter().map(|x| x.hidden).collect();
86 let mut worker: Worker<ConfigMMItem> = match cc.split {
88 Split::Delimiter(_) | Split::Regexes(_) => {
89 let names: Vec<Arc<str>> = if cc.names.is_empty() {
90 (0..cc.max_cols())
91 .map(|n| Arc::from(n.to_string()))
92 .collect()
93 } else {
94 cc.names
95 .iter()
96 .map(|s| Arc::from(s.name.as_str()))
97 .collect()
98 };
99 Worker::new_indexable(names)
100 }
101 Split::None => Worker::new_indexable([""]),
102 };
103
104 #[cfg(feature = "experimental")]
105 worker.reverse_items(worker_config.reverse);
106 #[cfg(feature = "experimental")]
107 worker.set_stability(worker_config.sort_threshold);
108
109 let injector = worker.injector();
110
111 let col_count = worker.columns.len();
113
114 let splitter: SplitterFn<Either<String, Text>> = match cc.split {
116 Split::Delimiter(ref rg) => {
117 let rg = rg.clone();
118 Arc::new(move |s| {
119 let s = &s.to_cow();
120
121 let mut ranges = ArrayVec::new();
122 let mut last_end = 0;
123 for m in rg.find_iter(s).take(col_count) {
124 ranges.push((last_end, m.start()));
125 last_end = m.end();
126 }
127 ranges.push((last_end, s.len()));
128 ranges
129 })
130 }
131 Split::Regexes(ref rgs) => {
132 let rgs = rgs.clone(); Arc::new(move |s| {
134 let s = &s.to_cow();
135 let mut ranges = ArrayVec::new();
136
137 for re in rgs.iter().take(col_count) {
138 if let Some(m) = re.find(s) {
139 ranges.push((m.start(), m.end()));
140 } else {
141 ranges.push((0, 0));
142 }
143 }
144 ranges
145 })
146 }
147 Split::None => Arc::new(|s| ArrayVec::from_iter([(0, s.to_cow().len())])),
148 };
149 let injector = IndexedInjector::new_globally_indexed(injector);
150 let injector = SegmentedInjector::new(injector, splitter.clone());
151 let injector = AnsiInjector::new(injector, preprocess_config);
152
153 let selection_set = if render_config.results.multi {
156 Selector::new(Indexed::identifier)
157
158 } else {
159 Selector::new(Indexed::identifier).disabled()
160 };
161
162 let event_handlers = EventHandlers::new();
163 let interrupt_handlers = InterruptHandlers::new();
164 let formatter = Arc::new(worker.default_format_fn::<true>(|item| item.to_cow()));
165
166 let new = Matchmaker {
167 worker,
168 render_config,
169 tui_config,
170 exit_config,
171 selector: selection_set,
172 event_handlers,
173 interrupt_handlers,
174 };
175
176 let misc = OddEnds {
177 formatter,
178 splitter,
179 hidden_columns
180 };
181
182 (new, injector, misc)
183 }
184}
185
186impl<T: SSS, S: Selection> Matchmaker<T, S> {
187 pub fn new(worker: Worker<T>, selector: Selector<T, S>) -> Self {
188 Matchmaker {
189 worker,
190 render_config: RenderConfig::default(),
191 tui_config: TerminalConfig::default(),
192 exit_config: ExitConfig::default(),
193 selector,
194 event_handlers: EventHandlers::new(),
195 interrupt_handlers: InterruptHandlers::new(),
196 }
197 }
198
199 pub fn config_render(&mut self, render: RenderConfig) -> &mut Self {
201 self.render_config = render;
202 self
203 }
204 pub fn config_tui(&mut self, tui: TerminalConfig) -> &mut Self {
206 self.tui_config = tui;
207 self
208 }
209 pub fn config_exit(&mut self, exit: ExitConfig) -> &mut Self {
211 self.exit_config = exit;
212 self
213 }
214 pub fn register_event_handler<F>(&mut self, event: Event, handler: F)
216 where
217 F: Fn(&mut MMState<'_, '_, T, S>, &Event) + 'static,
218 {
219 let boxed = Box::new(handler);
220 self.register_boxed_event_handler(event, boxed);
221 }
222 pub fn register_boxed_event_handler(
224 &mut self,
225 event: Event,
226 handler: DynamicMethod<T, S, Event>,
227 ) {
228 self.event_handlers.set(event, handler);
229 }
230 pub fn register_interrupt_handler<F>(&mut self, interrupt: Interrupt, handler: F)
232 where
233 F: Fn(&mut MMState<'_, '_, T, S>) + 'static,
234 {
235 let boxed = Box::new(handler);
236 self.register_boxed_interrupt_handler(interrupt, boxed);
237 }
238 pub fn register_boxed_interrupt_handler(
240 &mut self,
241 variant: Interrupt,
242 handler: BoxedHandler<T, S>,
243 ) {
244 self.interrupt_handlers.set(variant, handler);
245 }
246
247 pub async fn pick<A: ActionExt>(self, builder: PickOptions<'_, T, S, A>) -> Result<Vec<S>> {
249 let PickOptions {
250 previewer,
251 ext_handler,
252 ext_aliaser,
253 #[cfg(feature = "bracketed-paste")]
254 paste_handler,
255 overlay_config,
256 hidden_columns,
257 ..
258 } = builder;
259
260 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
261 return Ok(self
262 .selector
263 .identify_to_vec([self.worker.get_nth(0).unwrap()]));
264 }
265
266 let mut event_loop = if let Some(e) = builder.event_loop {
267 e
268 } else if let Some(binds) = builder.binds {
269 EventLoop::with_binds(binds).with_tick_rate(self.render_config.tick_rate())
270 } else {
271 EventLoop::new()
272 };
273
274 let mut wait = false;
275 if let Some(path) = self.exit_config.last_key_path.clone() && !path.is_empty() {
276 event_loop.record_last_key(path);
277 wait = true;
278 }
279
280 let preview = match previewer {
282 Some(Either::Left(view)) => Some(view),
283 Some(Either::Right(mut previewer)) => {
284 let view = previewer.view();
285 previewer.connect_controller(event_loop.controller());
286
287 tokio::spawn(async move {
288 let _ = previewer.run().await;
289 });
290
291 Some(view)
292 }
293 _ => None,
294 };
295
296 let (render_tx, render_rx) = builder
297 .channel
298 .unwrap_or_else(tokio::sync::mpsc::unbounded_channel);
299 event_loop.add_tx(render_tx.clone());
300
301 let mut tui =
302 tui::Tui::new(self.tui_config).map_err(|e| MatchError::TUIError(e.to_string()))?;
303 tui.enter()
304 .map_err(|e| MatchError::TUIError(e.to_string()))?;
305
306 let event_controller = event_loop.controller();
308 let event_loop_handle = tokio::spawn(async move {
309 let _ = event_loop.run().await;
310 });
311 log::debug!("event loop started");
312
313 let overlay_ui = if builder.overlays.is_empty() {
314 None
315 } else {
316 Some(OverlayUI::new(
317 builder.overlays.into_boxed_slice(),
318 overlay_config.unwrap_or_default(),
319 ))
320 };
321
322 tui.redraw();
324
325 let ret = if let Some(matcher) = builder.matcher {
326 let (ui, picker, footer, preview) = UI::new(
327 self.render_config,
328 matcher,
329 self.worker,
330 self.selector,
331 preview,
332 &mut tui,
333 hidden_columns
334 );
335 render::render_loop(
336 ui,
337 picker,
338 footer,
339 preview,
340 tui,
341 overlay_ui,
342 self.exit_config,
343 render_rx,
344 event_controller,
345 (self.event_handlers, self.interrupt_handlers),
346 ext_handler,
347 ext_aliaser,
348 #[cfg(feature = "bracketed-paste")]
349 paste_handler,
350 )
351 .await
352 } else {
353 let mut matcher = nucleo::Matcher::new(nucleo::Config::DEFAULT);
354 let (ui, picker, footer, preview) = UI::new(
355 self.render_config,
356 &mut matcher,
357 self.worker,
358 self.selector,
359 preview,
360 &mut tui,
361 hidden_columns
362 );
363 render::render_loop(
364 ui,
365 picker,
366 footer,
367 preview,
368 tui,
369 overlay_ui,
370 self.exit_config,
371 render_rx,
372 event_controller,
373 (self.event_handlers, self.interrupt_handlers),
374 ext_handler,
375 ext_aliaser,
376 #[cfg(feature = "bracketed-paste")]
377 paste_handler,
378 )
379 .await
380 };
381 if wait {
382 let _ = event_loop_handle.await;
383 log::debug!("event loop finished");
384 }
385
386 ret
387 }
388
389 pub async fn pick_default(self) -> Result<Vec<S>> {
390 self.pick::<NullActionExt>(PickOptions::new()).await
391 }
392 }
393
394 #[ext(MatchResultExt)]
395 impl<T> Result<T> {
396 pub fn first<S>(self) -> Result<S>
398 where
399 T: IntoIterator<Item = S>,
400 {
401 match self {
402 Ok(v) => v.into_iter().next().ok_or(MatchError::NoMatch),
403 Err(e) => Err(e),
404 }
405 }
406
407 pub fn abort(self) -> Result<T> {
409 match self {
410 Err(MatchError::Abort(x)) => std::process::exit(x),
411 _ => self,
412 }
413 }
414 }
415
416 pub type PasteHandler<T, S> =
420 Box<dyn FnMut(String, &MMState<'_, '_, T, S>) -> String + Send + Sync + 'static>;
421
422 pub type ActionExtHandler<T, S, A> =
423 Box<dyn FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static>;
424
425 pub type ActionAliaser<T, S, A> =
426 Box<dyn FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A> + Send + Sync + 'static>;
427
428 pub struct PickOptions<'a, T: SSS, S: Selection, A: ActionExt = NullActionExt> {
430 matcher: Option<&'a mut nucleo::Matcher>,
431 matcher_config: nucleo::Config,
432
433 event_loop: Option<EventLoop<A>>,
434 binds: Option<BindMap<A>>,
435
436 ext_handler: Option<ActionExtHandler<T, S, A>>,
437 ext_aliaser: Option<ActionAliaser<T, S, A>>,
438 #[cfg(feature = "bracketed-paste")]
439 paste_handler: Option<PasteHandler<T, S>>,
440
441 overlays: Vec<Box<dyn Overlay<A = A>>>,
442 overlay_config: Option<OverlayConfig>,
443 previewer: Option<Either<Preview, Previewer>>,
444
445 hidden_columns: Vec<bool>,
446
447 pub channel: Option<(
453 RenderSender<A>,
454 tokio::sync::mpsc::UnboundedReceiver<crate::message::RenderCommand<A>>,
455 )>,
456 }
457
458 impl<'a, T: SSS, S: Selection, A: ActionExt> PickOptions<'a, T, S, A> {
459 pub const fn new() -> Self {
460 Self {
461 matcher: None,
462 event_loop: None,
463 previewer: None,
464 binds: None,
465 matcher_config: nucleo::Config::DEFAULT,
466 ext_handler: None,
467 ext_aliaser: None,
468 #[cfg(feature = "bracketed-paste")]
469 paste_handler: None,
470 overlay_config: None,
471 overlays: Vec::new(),
472 channel: None,
473 hidden_columns: Vec::new()
474 }
475 }
476
477 pub fn with_binds(binds: BindMap<A>) -> Self {
478 let mut ret = Self::new();
479 ret.binds = Some(binds);
480 ret
481 }
482
483 pub fn with_matcher(matcher: &'a mut nucleo::Matcher) -> Self {
484 let mut ret = Self::new();
485 ret.matcher = Some(matcher);
486 ret
487 }
488
489 pub fn binds(mut self, binds: BindMap<A>) -> Self {
490 self.binds = Some(binds);
491 self
492 }
493
494 pub fn event_loop(mut self, event_loop: EventLoop<A>) -> Self {
495 self.event_loop = Some(event_loop);
496 self
497 }
498
499 pub fn previewer(mut self, previewer: Previewer) -> Self {
503 self.previewer = Some(Either::Right(previewer));
504 self
505 }
506
507 pub fn preview(mut self, preview: Preview) -> Self {
510 self.previewer = Some(Either::Left(preview));
511 self
512 }
513
514 pub fn matcher(mut self, matcher_config: nucleo::Config) -> Self {
515 self.matcher_config = matcher_config;
516 self
517 }
518
519 pub fn hidden_columns(mut self, hidden_columns: Vec<bool>) -> Self {
520 self.hidden_columns = hidden_columns;
521 self
522 }
523
524 pub fn ext_handler<F>(mut self, handler: F) -> Self
525 where
526 F: FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static,
527 {
528 self.ext_handler = Some(Box::new(handler));
529 self
530 }
531
532 pub fn ext_aliaser<F>(mut self, aliaser: F) -> Self
533 where
534 F: FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A>
535 + Send
536 + Sync
537 + 'static,
538 {
539 self.ext_aliaser = Some(Box::new(aliaser));
540 self
541 }
542
543 #[cfg(feature = "bracketed-paste")]
544 pub fn paste_handler<F>(mut self, handler: F) -> Self
545 where
546 F: FnMut(String, &MMState<'_, '_, T, S>) -> String
547 + Send
548 + Sync
549 + 'static,
550 {
551 self.paste_handler = Some(Box::new(handler));
552 self
553 }
554
555 pub fn overlay<O>(mut self, overlay: O) -> Self
556 where
557 O: Overlay<A = A> + 'static,
558 {
559 self.overlays.push(Box::new(overlay));
560 self
561 }
562
563 pub fn overlay_config(mut self, overlay: OverlayConfig) -> Self {
564 self.overlay_config = Some(overlay);
565 self
566 }
567
568 pub fn render_tx(&mut self) -> RenderSender<A> {
569 if let Some((s, _)) = &self.channel {
570 s.clone()
571 } else {
572 let channel = tokio::sync::mpsc::unbounded_channel();
573 let ret = channel.0.clone();
574 self.channel = Some(channel);
575 ret
576 }
577 }
578 }
579
580 impl<'a, T: SSS, S: Selection, A: ActionExt> Default for PickOptions<'a, T, S, A> {
581 fn default() -> Self {
582 Self::new()
583 }
584 }
585
586 impl<T: SSS, S: Selection> Matchmaker<T, S> {
589 pub fn register_print_handler(
592 &mut self,
593 print_handle: AppendOnly<String>,
594 output_separator: String,
595 formatter: Arc<RenderFn<T>>,
596 ) {
597 self.register_interrupt_handler(Interrupt::Print, move |state| {
598 if let Some(t) = state.current_raw() {
599 let s = formatter(t, state.payload());
600 if atty::is(atty::Stream::Stdout) {
601 print_handle.push(s);
602 } else {
603 print!("{}{}", s, output_separator);
604 }
605 };
606 });
607 }
608
609 pub fn register_execute_handler(&mut self, formatter: Arc<RenderFn<T>>) {
614 self.register_interrupt_handler(Interrupt::Execute, move |state| {
615 let template = state.payload();
616 if !template.is_empty()
617 && let Some(t) = state.current_raw()
618 {
619 let cmd = formatter(t, template);
620 let mut vars = state.make_env_vars();
621
622 let preview_cmd = formatter(t, state.preview_payload());
623 let extra = env_vars!(
624 "FZF_PREVIEW_COMMAND" => preview_cmd,
625 );
626 vars.extend(extra);
627
628 if let Some(mut child) = Command::from_script(&cmd)
629 .envs(vars)
630 .stdin(maybe_tty())
631 ._spawn()
632 {
633 match child.wait() {
634 Ok(i) => {
635 info!("Command [{cmd}] exited with {i}")
636 }
637 Err(e) => {
638 info!("Failed to wait on command [{cmd}]: {e}")
639 }
640 }
641 }
642 };
643 });
644 }
645
646 pub fn register_become_handler(&mut self, formatter: Arc<RenderFn<T>>) {
651 self.register_interrupt_handler(Interrupt::Become, move |state| {
652 let template = state.payload();
653 if !template.is_empty()
654 && let Some(t) = state.current_raw()
655 {
656 let cmd = formatter(t, template);
657 let mut vars = state.make_env_vars();
658
659 let preview_cmd = formatter(t, state.preview_payload());
660 let extra = env_vars!(
661 "FZF_PREVIEW_COMMAND" => preview_cmd,
662 );
663 vars.extend(extra);
664 debug!("Becoming: {cmd}");
665
666 Command::from_script(&cmd).envs(vars)._exec()
667 }
668 });
669 }
670 }
671
672 pub fn make_previewer<T: SSS, S: Selection>(
675 mm: &mut Matchmaker<T, S>,
676 previewer_config: PreviewerConfig, formatter: Arc<RenderFn<T>>,
678 help_str: Text<'static>,
679 ) -> Previewer {
680 let (previewer, tx) = Previewer::new(previewer_config);
682 let preview_tx = tx.clone();
683
684 mm.register_event_handler(Event::CursorChange | Event::PreviewChange, move |state, _| {
686 if state.preview_visible &&
687 let Some(t) = state.current_raw() &&
688 let m = state.preview_payload() &&
689 !m.is_empty()
690 {
691 let cmd = formatter(t, m);
692 let mut envs = state.make_env_vars();
693 let extra = env_vars!(
694 "COLUMNS" => state.previewer_area().map_or("0".to_string(), |r| r.width.to_string()),
695 "LINES" => state.previewer_area().map_or("0".to_string(), |r| r.height.to_string()),
696 );
697 envs.extend(extra);
698
699 let msg = PreviewMessage::Run(cmd.clone(), envs);
700 _log!("{cmd:?}");
701 if preview_tx.send(msg.clone()).is_err() {
702 warn!("Failed to send to preview: {}", msg)
703 }
704
705 let target = state.preview_ui.as_ref().and_then(|p| p.config.scroll.index.as_ref().and_then(|index_col| {
706 state.current_raw().and_then(|item| {
707 state.picker_ui.worker.format_with(item, index_col).and_then(|t| t.parse::<isize>().ok())
708 })
709 })).unwrap_or(0); if let Some(p) = state.preview_ui {
712 p.set_target(target);
713 };
714
715 } else if preview_tx.send(PreviewMessage::Stop).is_err() {
716 warn!("Failed to send to preview: stop")
717 }
718
719 state.preview_set_payload = None;
720 });
721
722 mm.register_event_handler(Event::PreviewSet, move |state, _event| {
723 if state.preview_visible {
724 let msg = if let Some(m) = state.preview_set_payload() {
725 let m = if m.is_empty() && !help_str.lines.is_empty() {
726 help_str.clone()
727 } else {
728 Text::from(m)
729 };
730 PreviewMessage::Set(m)
731 } else {
732 PreviewMessage::Unset
733 };
734
735 if tx.send(msg.clone()).is_err() {
736 warn!("Failed to send: {}", msg)
737 }
738 }
739 });
740
741 previewer
742 }
743
744 fn maybe_tty() -> Stdio {
745 if let Ok(tty) = std::fs::File::open("/dev/tty") {
746 Stdio::from(tty)
748 } else {
749 log::error!("Failed to open /dev/tty");
750 Stdio::inherit()
751 }
752 }
753
754 impl<T: SSS + Debug, S: Selection + Debug> Debug for Matchmaker<T, S> {
757 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
758 f.debug_struct("Matchmaker")
759 .field("render_config", &self.render_config)
761 .field("tui_config", &self.tui_config)
762 .field("selection_set", &self.selector)
763 .field("event_handlers", &self.event_handlers)
764 .field("interrupt_handlers", &self.interrupt_handlers)
765 .finish()
766 }
767 }
768