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