1use std::{
2 fmt::{self, Debug, Formatter},
3 process::{Command, Stdio},
4 sync::Arc,
5};
6
7use arrayvec::ArrayVec;
8use cli_boilerplate_automation::{_log, broc::CommandExt, env_vars, prints};
9use easy_ext::ext;
10use log::{debug, info, warn};
11use ratatui::text::Text;
12
13use crate::{
14 MatchError, RenderFn, Result, SSS, Selection, Selector, SplitterFn,
15 action::{ActionAliaser, ActionExt, ActionExtHandler, NullActionExt},
16 binds::BindMap,
17 config::{ExitConfig, PreviewerConfig, RenderConfig, Split, TerminalConfig, WorkerConfig},
18 efx,
19 event::{EventLoop, RenderSender},
20 message::{Event, Interrupt},
21 nucleo::{
22 Indexed, Segmented, Worker,
23 injector::{IndexedInjector, Injector, SegmentedInjector, WorkerInjector},
24 },
25 preview::{
26 AppendOnly, Preview,
27 previewer::{PreviewMessage, Previewer},
28 },
29 render::{self, DynamicMethod, Effects, EventHandlers, InterruptHandlers, MMState},
30 tui,
31 ui::{Overlay, OverlayUI, UI},
32};
33
34pub struct Matchmaker<T: SSS, S: Selection = T> {
42 pub worker: Worker<T>,
43 pub render_config: RenderConfig,
44 pub tui_config: TerminalConfig,
45 pub exit_config: ExitConfig,
46 pub selector: Selector<T, S>,
47 pub event_handlers: EventHandlers<T, S>,
48 pub interrupt_handlers: InterruptHandlers<T, S>,
49 pub preview: Option<Preview>,
50}
51
52pub struct OddEnds {
56 pub formatter: Arc<RenderFn<Indexed<Segmented<String>>>>,
57 pub splitter: SplitterFn<String>,
58}
59
60pub type ConfigInjector = SegmentedInjector<
61 String,
62 IndexedInjector<Segmented<String>, WorkerInjector<Indexed<Segmented<String>>>>,
63>;
64pub type ConfigMatchmaker = Matchmaker<Indexed<Segmented<String>>, Segmented<String>>;
65
66impl ConfigMatchmaker {
67 pub fn new_from_config(
69 render_config: RenderConfig,
70 tui_config: TerminalConfig,
71 worker_config: WorkerConfig,
72 ) -> (Self, ConfigInjector, OddEnds) {
73 let cc = worker_config.columns;
74
75 let worker: Worker<Indexed<Segmented<String>>> = match cc.split {
76 Split::Delimiter(_) | Split::Regexes(_) => {
77 let names: Vec<Arc<str>> = if cc.names.is_empty() {
78 (0..cc.max_cols())
79 .map(|n| Arc::from(n.to_string()))
80 .collect()
81 } else {
82 cc.names
83 .iter()
84 .map(|s| Arc::from(s.name.as_str()))
85 .collect()
86 };
87 Worker::new_indexable(names)
88 }
89 Split::None => Worker::new_indexable([""]),
90 };
91
92 let injector = worker.injector();
93
94 let col_count = worker.columns.len();
96
97 let splitter: SplitterFn<String> = match cc.split {
99 Split::Delimiter(ref rg) => {
100 let rg = rg.clone();
101 Arc::new(move |s| {
102 let mut ranges = ArrayVec::new();
103 let mut last_end = 0;
104 for m in rg.find_iter(s).take(col_count) {
105 ranges.push((last_end, m.start()));
106 last_end = m.end();
107 }
108 ranges.push((last_end, s.len()));
109 ranges
110 })
111 }
112 Split::Regexes(ref rgs) => {
113 let rgs = rgs.clone(); Arc::new(move |s| {
115 let mut ranges = ArrayVec::new();
116 for re in rgs.iter().take(col_count) {
117 if let Some(m) = re.find(s) {
118 ranges.push((m.start(), m.end()));
119 } else {
120 ranges.push((0, 0));
121 }
122 }
123 ranges
124 })
125 }
126 Split::None => Arc::new(|s| ArrayVec::from_iter([(0, s.len())])),
127 };
128 let injector = IndexedInjector::new_globally_indexed(injector);
129 let injector = SegmentedInjector::new(injector, splitter.clone());
130
131 let selection_set = Selector::new(Indexed::identifier);
132
133 let event_handlers = EventHandlers::new();
134 let interrupt_handlers = InterruptHandlers::new();
135 let formatter = Arc::new(
136 worker.make_format_fn::<true>(|item| std::borrow::Cow::Borrowed(&item.inner.inner)),
137 );
138
139 let new = Matchmaker {
140 worker,
141 render_config,
142 tui_config,
143 exit_config: worker_config.exit,
144 selector: selection_set,
145 event_handlers,
146 interrupt_handlers,
147 preview: None,
148 };
149
150 let misc = OddEnds {
151 formatter,
152 splitter,
153 };
154
155 (new, injector, misc)
156 }
157}
158
159impl<T: SSS, S: Selection> Matchmaker<T, S> {
160 pub fn new(worker: Worker<T>, selector: Selector<T, S>) -> Self {
161 Matchmaker {
162 worker,
163 render_config: RenderConfig::default(),
164 tui_config: TerminalConfig::default(),
165 exit_config: ExitConfig::default(),
166 selector,
167 event_handlers: EventHandlers::new(),
168 interrupt_handlers: InterruptHandlers::new(),
169 preview: None,
170 }
171 }
172
173 pub fn connect_preview(&mut self, preview: Preview) {
189 self.preview = Some(preview);
190 }
191
192 pub fn config_render(&mut self, render: RenderConfig) -> &mut Self {
194 self.render_config = render;
195 self
196 }
197 pub fn config_tui(&mut self, tui: TerminalConfig) -> &mut Self {
199 self.tui_config = tui;
200 self
201 }
202 pub fn config_exit(&mut self, exit: ExitConfig) -> &mut Self {
204 self.exit_config = exit;
205 self
206 }
207 pub fn register_event_handler<F, I>(&mut self, events: I, handler: F)
209 where
210 F: Fn(&mut MMState<'_, T, S>, &Event) -> Effects + SSS,
211 I: IntoIterator<Item = Event>,
212 {
213 let boxed = Box::new(handler);
214 self.register_boxed_event_handler(events, boxed);
215 }
216 pub fn register_boxed_event_handler<I>(
218 &mut self,
219 events: I,
220 handler: DynamicMethod<T, S, Event>,
221 ) where
222 I: IntoIterator<Item = Event>,
223 {
224 let events_vec: Vec<_> = events.into_iter().collect();
225 self.event_handlers.set(events_vec, handler);
226 }
227 pub fn register_interrupt_handler<F>(&mut self, interrupt: Interrupt, handler: F)
229 where
230 F: Fn(&mut MMState<'_, T, S>, &Interrupt) -> Effects + SSS,
231 {
232 let boxed = Box::new(handler);
233 self.register_boxed_interrupt_handler(interrupt, boxed);
234 }
235 pub fn register_boxed_interrupt_handler(
237 &mut self,
238 variant: Interrupt,
239 handler: DynamicMethod<T, S, Interrupt>,
240 ) {
241 self.interrupt_handlers.set(variant, handler);
242 }
243
244 pub async fn pick<A: ActionExt>(
246 mut self,
247 builder: PickOptions<'_, T, S, A>,
248 ) -> Result<Vec<S>, MatchError> {
249 let PickOptions {
250 previewer,
251 ext_handler,
252 ext_aliaser,
253 #[cfg(feature = "bracketed-paste")]
254 paste_handler,
255 ..
256 } = builder;
257
258 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
259 return Ok(self
260 .selector
261 .identify_to_vec([self.worker.get_nth(0).unwrap()]));
262 }
263
264 let mut event_loop = if let Some(e) = builder.event_loop {
265 e
266 } else if let Some(binds) = builder.binds {
267 EventLoop::with_binds(binds).with_tick_rate(self.render_config.tick_rate())
268 } else {
269 EventLoop::new()
270 };
271
272 if let Some(mut previewer) = previewer {
274 if self.preview.is_none() {
275 self.preview = Some(previewer.view());
276 }
277 previewer.connect_controller(event_loop.get_controller());
278 tokio::spawn(async move {
279 let _ = previewer.run().await;
280 });
281 }
282
283 let (render_tx, render_rx) = builder
284 .channel
285 .unwrap_or_else(tokio::sync::mpsc::unbounded_channel);
286 event_loop.add_tx(render_tx.clone());
287
288 let mut tui =
289 tui::Tui::new(self.tui_config).map_err(|e| MatchError::TUIError(e.to_string()))?;
290 tui.enter()
291 .map_err(|e| MatchError::TUIError(e.to_string()))?;
292
293 let event_controller = event_loop.get_controller();
295 tokio::spawn(async move {
296 let _ = event_loop.run().await;
297 });
298 log::debug!("event loop started");
299
300 let overlay_ui = if builder.overlays.is_empty() {
301 None
302 } else {
303 Some(OverlayUI::new(
304 builder.overlays.into_boxed_slice(),
305 self.render_config.overlay.take().unwrap_or_default(),
306 ))
307 };
308
309 tui.redraw();
311
312 if let Some(matcher) = builder.matcher {
313 let (ui, picker, preview) = UI::new(
314 self.render_config,
315 matcher,
316 self.worker,
317 self.selector,
318 self.preview,
319 &mut tui,
320 );
321 render::render_loop(
322 ui,
323 picker,
324 preview,
325 tui,
326 overlay_ui,
327 self.exit_config,
328 render_rx,
329 event_controller,
330 (self.event_handlers, self.interrupt_handlers),
331 ext_handler,
332 ext_aliaser,
333 #[cfg(feature = "bracketed-paste")]
334 paste_handler,
335 )
336 .await
337 } else {
338 let mut matcher = nucleo::Matcher::new(nucleo::Config::DEFAULT);
339 let (ui, picker, preview) = UI::new(
340 self.render_config,
341 &mut matcher,
342 self.worker,
343 self.selector,
344 self.preview,
345 &mut tui,
346 );
347 render::render_loop(
348 ui,
349 picker,
350 preview,
351 tui,
352 overlay_ui,
353 self.exit_config,
354 render_rx,
355 event_controller,
356 (self.event_handlers, self.interrupt_handlers),
357 ext_handler,
358 ext_aliaser,
359 #[cfg(feature = "bracketed-paste")]
360 paste_handler,
361 )
362 .await
363 }
364 }
365
366 pub async fn pick_default(self) -> Result<Vec<S>, MatchError> {
367 self.pick::<NullActionExt>(PickOptions::new()).await
368 }
369}
370
371#[ext(MatchResultExt)]
372impl<T> Result<T, MatchError> {
373 pub fn first<S>(self) -> Result<S, MatchError>
375 where
376 T: IntoIterator<Item = S>,
377 {
378 match self {
379 Ok(v) => v.into_iter().next().ok_or(MatchError::NoMatch),
380 Err(e) => Err(e),
381 }
382 }
383
384 pub fn abort(self) -> Result<T, MatchError> {
386 match self {
387 Err(MatchError::Abort(x)) => std::process::exit(x),
388 _ => self,
389 }
390 }
391}
392
393pub type PasteHandler<T, S> = fn(String, &MMState<'_, T, S>) -> String;
397pub struct PickOptions<'a, T: SSS, S: Selection, A: ActionExt = NullActionExt> {
399 matcher: Option<&'a mut nucleo::Matcher>,
400 matcher_config: nucleo::Config,
401
402 event_loop: Option<EventLoop<A>>,
403 binds: Option<BindMap<A>>,
404
405 ext_handler: Option<ActionExtHandler<T, S, A>>,
406 ext_aliaser: Option<ActionAliaser<T, S, A>>,
407 #[cfg(feature = "bracketed-paste")]
408 paste_handler: Option<PasteHandler<T, S>>,
409
410 overlays: Vec<Box<dyn Overlay<A = A>>>,
411 previewer: Option<Previewer>,
412
413 pub channel: Option<(
419 RenderSender<A>,
420 tokio::sync::mpsc::UnboundedReceiver<crate::message::RenderCommand<A>>,
421 )>,
422}
423
424impl<'a, T: SSS, S: Selection, A: ActionExt> PickOptions<'a, T, S, A> {
426 pub const fn new() -> Self {
427 Self {
428 matcher: None,
429 event_loop: None,
430 previewer: None,
431 binds: None,
432 matcher_config: nucleo::Config::DEFAULT,
433 ext_handler: None,
434 ext_aliaser: None,
435 #[cfg(feature = "bracketed-paste")]
436 paste_handler: None,
437 overlays: Vec::new(),
438 channel: None,
439 }
440 }
441
442 pub fn with_binds(binds: BindMap<A>) -> Self {
443 let mut ret = Self::new();
444 ret.binds = Some(binds);
445 ret
446 }
447
448 pub fn with_matcher(matcher: &'a mut nucleo::Matcher) -> Self {
449 let mut ret = Self::new();
450 ret.matcher = Some(matcher);
451 ret
452 }
453
454 pub fn binds(mut self, binds: BindMap<A>) -> Self {
455 self.binds = Some(binds);
456 self
457 }
458
459 pub fn event_loop(mut self, event_loop: EventLoop<A>) -> Self {
460 self.event_loop = Some(event_loop);
461 self
462 }
463
464 pub fn previewer(mut self, previewer: Previewer) -> Self {
466 self.previewer = Some(previewer);
467 self
468 }
469
470 pub fn matcher(mut self, matcher_config: nucleo::Config) -> Self {
471 self.matcher_config = matcher_config;
472 self
473 }
474
475 pub fn ext_handler(mut self, handler: ActionExtHandler<T, S, A>) -> Self {
476 self.ext_handler = Some(handler);
477 self
478 }
479
480 pub fn ext_aliaser(mut self, aliaser: ActionAliaser<T, S, A>) -> Self {
481 self.ext_aliaser = Some(aliaser);
482 self
483 }
484
485 #[cfg(feature = "bracketed-paste")]
486 pub fn paste_handler(mut self, handler: PasteHandler<T, S>) -> Self {
487 self.paste_handler = Some(handler);
488 self
489 }
490
491 pub fn overlay<O>(mut self, overlay: O) -> Self
492 where
493 O: Overlay<A = A> + 'static,
494 {
495 self.overlays.push(Box::new(overlay));
496 self
497 }
498
499 pub fn get_tx(&mut self) -> RenderSender<A> {
500 if let Some((s, _)) = &self.channel {
501 s.clone()
502 } else {
503 let channel = tokio::sync::mpsc::unbounded_channel();
504 let ret = channel.0.clone();
505 self.channel = Some(channel);
506 ret
507 }
508 }
509
510 }
527
528impl<'a, T: SSS, S: Selection, A: ActionExt> Default for PickOptions<'a, T, S, A> {
529 fn default() -> Self {
530 Self::new()
531 }
532}
533
534impl<T: SSS, S: Selection> Matchmaker<T, S> {
537 pub fn register_print_handler(
538 &mut self,
539 print_handle: AppendOnly<String>,
540 formatter: Arc<RenderFn<T>>,
541 ) {
542 self.register_interrupt_handler(Interrupt::Print("".into()), move |state, i| {
543 if let Interrupt::Print(template) = i
544 && let Some(t) = state.current_raw()
545 {
546 let s = formatter(t, template);
547 if atty::is(atty::Stream::Stdout) {
548 print_handle.push(s);
549 } else {
550 prints!(s);
551 }
552 };
553 efx![]
554 });
555 }
556
557 pub fn register_execute_handler(&mut self, formatter: Arc<RenderFn<T>>) {
558 let preview_formatter = formatter.clone();
559
560 self.register_interrupt_handler(Interrupt::Execute("".into()), move |state, interrupt| {
561 if let Interrupt::Execute(template) = interrupt
562 && !template.is_empty()
563 && let Some(t) = state.current_raw()
564 {
565 let cmd = formatter(t, template);
566 let mut vars = state.make_env_vars();
567 let preview_cmd = preview_formatter(t, state.preview_payload());
568 let extra = env_vars!(
569 "FZF_PREVIEW_COMMAND" => preview_cmd,
570 );
571 vars.extend(extra);
572 if let Some(mut child) = Command::from_script(&cmd)
573 .envs(vars)
574 .stdin(maybe_tty())
575 ._spawn()
576 {
577 match child.wait() {
578 Ok(i) => {
579 info!("Command [{cmd}] exited with {i}")
580 }
581 Err(e) => {
582 info!("Failed to wait on command [{cmd}]: {e}")
583 }
584 }
585 }
586 };
587 efx![]
588 });
589 }
590
591 pub fn register_become_handler(&mut self, formatter: Arc<RenderFn<T>>) {
592 let preview_formatter = formatter.clone();
593
594 self.register_interrupt_handler(Interrupt::Become("".into()), move |state, interrupt| {
595 if let Interrupt::Become(template) = interrupt
596 && !template.is_empty()
597 && let Some(t) = state.current_raw()
598 {
599 let cmd = formatter(t, template);
600 let mut vars = state.make_env_vars();
601
602 let preview_cmd = preview_formatter(t, state.preview_payload());
603 let extra = env_vars!(
604 "FZF_PREVIEW_COMMAND" => preview_cmd,
605 );
606 vars.extend(extra);
607 debug!("Becoming: {cmd}");
608
609 Command::from_script(&cmd).envs(vars)._exec()
610 }
611 efx![]
612 });
613 }
614}
615
616pub fn make_previewer<T: SSS, S: Selection>(
617 mm: &mut Matchmaker<T, S>,
618 previewer_config: PreviewerConfig, formatter: Arc<RenderFn<T>>,
620 help_str: Text<'static>,
621) -> Previewer {
622 let (previewer, tx) = Previewer::new(previewer_config);
624 mm.connect_preview(previewer.view());
625 let preview_tx = tx.clone();
626
627 mm.register_event_handler([Event::CursorChange, Event::PreviewChange], move |state, _| {
629 if state.preview_show &&
630 let Some(t) = state.current_raw() &&
631 let m = state.preview_payload() &&
632 !m.is_empty()
633 {
634 let cmd = formatter(t, m);
635 let mut envs = state.make_env_vars();
636 let extra = env_vars!(
637 "COLUMNS" => state.previewer_area().map_or("0".to_string(), |r| r.width.to_string()),
638 "LINES" => state.previewer_area().map_or("0".to_string(), |r| r.height.to_string()),
639 );
640 envs.extend(extra);
641
642 let msg = PreviewMessage::Run(cmd.clone(), envs);
643 _log!("{cmd:?}");
644 if preview_tx.send(msg.clone()).is_err() {
645 warn!("Failed to send to preview: {}", msg)
646 }
647 } else if preview_tx.send(PreviewMessage::Stop).is_err() {
648 warn!("Failed to send to preview: stop")
649 }
650
651 efx![render::Effect::ClearPreviewSet] });
653
654 mm.register_event_handler([Event::PreviewSet], move |state, _event| {
655 if state.preview_show {
656 let msg = if let Some(m) = state.preview_set_payload() {
657 let m = if m.is_empty() && !help_str.lines.is_empty() {
658 help_str.clone()
659 } else {
660 Text::from(m.clone())
661 };
662 PreviewMessage::Set(m.clone())
663 } else {
664 PreviewMessage::Unset
665 };
666
667 if tx.send(msg.clone()).is_err() {
668 warn!("Failed to send: {}", msg)
669 }
670 }
671 efx![]
672 });
673
674 previewer
675}
676
677fn maybe_tty() -> Stdio {
678 if let Ok(mut tty) = std::fs::File::open("/dev/tty") {
679 let _ = std::io::Write::flush(&mut tty); Stdio::from(tty)
681 } else {
682 log::error!("Failed to open /dev/tty");
683 Stdio::inherit()
684 }
685}
686
687impl<T: SSS + Debug, S: Selection + Debug> Debug for Matchmaker<T, S> {
690 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
691 f.debug_struct("Matchmaker")
692 .field("render_config", &self.render_config)
694 .field("tui_config", &self.tui_config)
695 .field("selection_set", &self.selector)
696 .field("event_handlers", &self.event_handlers)
697 .field("interrupt_handlers", &self.interrupt_handlers)
698 .field("previewer", &self.preview)
699 .finish()
700 }
701}