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