1use std::{
2 fmt::{self, Debug, Formatter},
3 process::{Command, Stdio},
4 sync::Arc,
5};
6
7use arrayvec::ArrayVec;
8use cba::{bath::PathExt, broc::CommandExt, ebog, 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 ColumnsConfig, ExitConfig, OverlayConfig, PreviewerConfig, RenderConfig, Split,
19 TerminalConfig, 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 {
59 pub splitter: SplitterFn<Either<String, Text<'static>>>,
60 pub hidden_columns: Vec<bool>,
61 pub has_error: bool
62}
63
64pub type ConfigInjector = AnsiInjector<
65SegmentedInjector<
66Either<String, Text<'static>>,
67IndexedInjector<Segmented<Either<String, Text<'static>>>, WorkerInjector<ConfigMMItem>>,
68>,
69>;
70pub type ConfigMatchmaker = Matchmaker<ConfigMMItem, Segmented<Either<String, Text<'static>>>>;
71pub type ConfigMMInnerItem = Segmented<Either<String, Text<'static>>>;
72pub type ConfigMMItem = Indexed<ConfigMMInnerItem>;
73
74impl ConfigMatchmaker {
75 #[allow(unused)]
76 pub fn new_from_config(
78 render_config: RenderConfig,
79 tui_config: TerminalConfig,
80 worker_config: WorkerConfig,
81 columns_config: ColumnsConfig,
82 exit_config: ExitConfig,
83 preprocess_config: PreprocessOptions,
84 ) -> (Self, ConfigInjector, OddEnds) {
85 let mut has_error = false;
86
87 let cc = columns_config;
88 let hidden_columns = cc.names.iter().map(|x| x.hidden).collect();
89 let init = !cc.names_from_zero as usize;
91 let mut worker: Worker<ConfigMMItem> = match cc.split {
92 Split::Delimiter(_) | Split::Regexes(_) => {
93 let names: Vec<Arc<str>> = if cc.names.is_empty() {
94 (init..(cc.max_cols() + init))
95 .map(|n| Arc::from(n.to_string()))
96 .collect()
97 } else {
98 cc.names
99 .iter()
100 .take(cc.max_cols())
101 .map(|s| Arc::from(s.name.as_str()))
102 .collect()
103 };
104 Worker::new_indexable(names, cc.default.as_ref().map(|x| x.0.as_str()))
105 }
106 Split::None => Worker::new_indexable([""], None),
107 };
108
109 #[cfg(feature = "experimental")]
110 worker.reverse_items(worker_config.reverse);
111 #[cfg(feature = "experimental")]
112 worker.set_stability(worker_config.sort_threshold);
113
114 let injector = worker.injector();
115
116 let col_count = worker.columns.len();
118
119 let splitter: SplitterFn<Either<String, Text>> = match cc.split {
121 Split::Delimiter(ref rg) => {
122 let rg = rg.clone();
123 let names = cc.names.clone();
124 let col_count = worker.columns.len();
125 let mut has_named_group = false;
126
127 let capture_to_idx: Vec<Option<usize>> = rg
129 .capture_names()
130 .enumerate()
131 .map(|(i, name_opt)| {
132 if i == 0 {
133 None
134 } else {
135 name_opt.and_then(|name| {
136 has_named_group = true;
137 names.iter().position(|n| n.name == name)
138 })
139 }
140 })
141 .collect();
142
143 let has_unnamed = rg.captures_len() > 1 && !has_named_group;
148
149 if has_named_group {
150 log::debug!("Named regex: {rg} with {} groups", capture_to_idx.len());
151 if capture_to_idx.iter().all(|x| x.is_none()) {
152 ebog!("No capture group matches a column name");
153 has_error = true;
154 }
155
156 Arc::new(move |s| {
158 let s = &s.to_cow();
159 let mut ranges = ArrayVec::from_iter(vec![(0, 0); col_count]);
160
161 if let Some(caps) = rg.captures(s) {
162 for (group_idx, col_idx_opt) in capture_to_idx.iter().enumerate().skip(1) {
163 if let Some(col_idx) = col_idx_opt {
164 if let Some(m) = caps.get(group_idx) {
165 ranges[*col_idx] = (m.start(), m.end());
166 }
167 }
168 }
169 }
170
171 ranges
172 })
173 } else if has_unnamed {
174 log::debug!("Unnamed regex: {rg} with {} groups", capture_to_idx.len());
175
176 Arc::new(move |s| {
178 let s = &s.to_cow();
179 let mut ranges = ArrayVec::from_iter(vec![(0, 0); col_count]);
180
181 if let Some(caps) = rg.captures(s) {
182 for (i, group) in caps.iter().skip(1).enumerate().take(col_count) {
183 if let Some(m) = group {
184 ranges[i] = (m.start(), m.end());
185 }
186 }
187 }
188
189 ranges
190 })
191 } else {
192 log::debug!("Delimiter regex: {rg}");
193
194 Arc::new(move |s| {
196 let s = &s.to_cow();
197 let mut ranges = ArrayVec::new();
198 let mut last_end = 0;
199
200 for m in rg.find_iter(s).take(col_count - 1) {
201 ranges.push((last_end, m.start()));
202 last_end = m.end();
203 }
204
205 ranges.push((last_end, s.len()));
206 ranges
207 })
208 }
209 }
210 Split::Regexes(ref rgs) => {
212 let rgs = rgs.clone(); Arc::new(move |s| {
214 let s = &s.to_cow();
215 let mut ranges = ArrayVec::new();
216
217 for re in rgs.iter().take(col_count) {
218 if let Some(m) = re.find(s) {
219 ranges.push((m.start(), m.end()));
220 } else {
221 ranges.push((0, 0));
222 }
223 }
224 ranges
225 })
226 }
227 Split::None => Arc::new(|s| ArrayVec::from_iter([(0, s.to_cow().len())])),
228 };
229 let injector = IndexedInjector::new_globally_indexed(injector);
230 let injector = SegmentedInjector::new(injector, splitter.clone());
231 let injector = AnsiInjector::new(injector, preprocess_config);
232
233 let selection_set = if render_config.results.multi {
234 Selector::new(Indexed::identifier)
235 } else {
236 Selector::new(Indexed::identifier).disabled()
237 };
238
239 let event_handlers = EventHandlers::new();
240 let interrupt_handlers = InterruptHandlers::new();
241
242 let new = Matchmaker {
243 worker,
244 render_config,
245 tui_config,
246 exit_config,
247 selector: selection_set,
248 event_handlers,
249 interrupt_handlers,
250 };
251
252 let misc = OddEnds {
253 splitter,
254 hidden_columns,
255 has_error
256 };
257
258 (new, injector, misc)
259 }
260}
261
262impl<T: SSS, S: Selection> Matchmaker<T, S> {
263 pub fn new(worker: Worker<T>, selector: Selector<T, S>) -> Self {
264 Matchmaker {
265 worker,
266 render_config: RenderConfig::default(),
267 tui_config: TerminalConfig::default(),
268 exit_config: ExitConfig::default(),
269 selector,
270 event_handlers: EventHandlers::new(),
271 interrupt_handlers: InterruptHandlers::new(),
272 }
273 }
274
275 pub fn config_render(&mut self, render: RenderConfig) -> &mut Self {
277 self.render_config = render;
278 self
279 }
280 pub fn config_tui(&mut self, tui: TerminalConfig) -> &mut Self {
282 self.tui_config = tui;
283 self
284 }
285 pub fn config_exit(&mut self, exit: ExitConfig) -> &mut Self {
287 self.exit_config = exit;
288 self
289 }
290 pub fn register_event_handler<F>(&mut self, event: Event, handler: F)
292 where
293 F: Fn(&mut MMState<'_, '_, T, S>, &Event) + 'static,
294 {
295 let boxed = Box::new(handler);
296 self.register_boxed_event_handler(event, boxed);
297 }
298 pub fn register_boxed_event_handler(
300 &mut self,
301 event: Event,
302 handler: DynamicMethod<T, S, Event>,
303 ) {
304 self.event_handlers.set(event, handler);
305 }
306 pub fn register_interrupt_handler<F>(&mut self, interrupt: Interrupt, handler: F)
308 where
309 F: Fn(&mut MMState<'_, '_, T, S>) + 'static,
310 {
311 let boxed = Box::new(handler);
312 self.register_boxed_interrupt_handler(interrupt, boxed);
313 }
314 pub fn register_boxed_interrupt_handler(
316 &mut self,
317 variant: Interrupt,
318 handler: BoxedHandler<T, S>,
319 ) {
320 self.interrupt_handlers.set(variant, handler);
321 }
322
323 pub async fn pick<A: ActionExt>(self, builder: PickOptions<'_, T, S, A>) -> Result<Vec<S>> {
325 let PickOptions {
326 previewer,
327 ext_handler,
328 ext_aliaser,
329 #[cfg(feature = "bracketed-paste")]
330 paste_handler,
331 overlay_config,
332 hidden_columns,
333 initializer,
334 ..
335 } = builder;
336
337 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
338 return Ok(self
339 .selector
340 .identify_to_vec([self.worker.get_nth(0).unwrap()]));
341 }
342
343 let mut event_loop = if let Some(e) = builder.event_loop {
344 e
345 } else if let Some(binds) = builder.binds {
346 EventLoop::with_binds(binds).with_tick_rate(self.render_config.tick_rate())
347 } else {
348 EventLoop::new()
349 };
350
351 let mut wait = false;
352 if let Some(path) = self.exit_config.last_key_path.clone()
353 && !path.is_empty()
354 {
355 event_loop.record_last_key(path);
356 wait = true;
357 }
358
359 let preview = match previewer {
360 Some(Either::Left(view)) => Some(view),
361 Some(Either::Right(mut previewer)) => {
362 let view = previewer.view();
363 previewer.connect_controller(event_loop.controller());
364
365 tokio::spawn(async move {
366 let _ = previewer.run().await;
367 });
368
369 Some(view)
370 }
371 _ => None,
372 };
373
374 let (render_tx, render_rx) = builder
375 .channel
376 .unwrap_or_else(tokio::sync::mpsc::unbounded_channel);
377 event_loop.add_tx(render_tx.clone());
378
379 let mut tui =
380 tui::Tui::new(self.tui_config).map_err(|e| MatchError::TUIError(e.to_string()))?;
381 tui.enter()
382 .map_err(|e| MatchError::TUIError(e.to_string()))?;
383
384 let event_controller = event_loop.controller();
386 let event_loop_handle = tokio::spawn(async move {
387 let _ = event_loop.run().await;
388 });
389 log::debug!("event loop started");
390
391 let overlay_ui = if builder.overlays.is_empty() {
392 None
393 } else {
394 Some(OverlayUI::new(
395 builder.overlays.into_boxed_slice(),
396 overlay_config.unwrap_or_default(),
397 ))
398 };
399
400 tui.redraw();
402
403 let matcher = if let Some(matcher) = builder.matcher {
404 matcher
405 } else {
406 &mut nucleo::Matcher::new(nucleo::Config::DEFAULT)
407 };
408
409 let (ui, picker, footer, preview) = UI::new(
410 self.render_config,
411 matcher,
412 self.worker,
413 self.selector,
414 preview,
415 &mut tui,
416 hidden_columns,
417 );
418
419 let ret = render::render_loop(
420 ui,
421 picker,
422 footer,
423 preview,
424 tui,
425 overlay_ui,
426 self.exit_config,
427 render_rx,
428 event_controller,
429 (self.event_handlers, self.interrupt_handlers),
430 ext_handler,
431 ext_aliaser,
432 initializer,
433 #[cfg(feature = "bracketed-paste")]
434 paste_handler,
435 )
436 .await;
437
438 if wait {
439 let _ = event_loop_handle.await;
440 log::debug!("event loop finished");
441 }
442
443 ret
444 }
445
446 pub async fn pick_default(self) -> Result<Vec<S>> {
447 self.pick::<NullActionExt>(PickOptions::new()).await
448 }
449 }
450
451 #[ext(MatchResultExt)]
452 impl<T> Result<T> {
453 pub fn first<S>(self) -> Result<S>
455 where
456 T: IntoIterator<Item = S>,
457 {
458 match self {
459 Ok(v) => v.into_iter().next().ok_or(MatchError::NoMatch),
460 Err(e) => Err(e),
461 }
462 }
463
464 pub fn abort(self) -> Result<T> {
466 match self {
467 Err(MatchError::Abort(x)) => std::process::exit(x),
468 _ => self,
469 }
470 }
471 }
472
473 pub type PasteHandler<T, S> =
477 Box<dyn FnMut(String, &MMState<'_, '_, T, S>) -> String + Send + Sync + 'static>;
478
479 pub type ActionExtHandler<T, S, A> =
480 Box<dyn FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static>;
481
482 pub type ActionAliaser<T, S, A> =
483 Box<dyn FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A> + Send + Sync + 'static>;
484
485 pub type Initializer<T, S> = Box<dyn FnOnce(&mut MMState<'_, '_, T, S>) + Send + Sync + 'static>;
486
487 pub struct PickOptions<'a, T: SSS, S: Selection, A: ActionExt = NullActionExt> {
489 matcher: Option<&'a mut nucleo::Matcher>,
490 matcher_config: nucleo::Config,
491
492 event_loop: Option<EventLoop<A>>,
493 binds: Option<BindMap<A>>,
494
495 ext_handler: Option<ActionExtHandler<T, S, A>>,
496 ext_aliaser: Option<ActionAliaser<T, S, A>>,
497 #[cfg(feature = "bracketed-paste")]
498 paste_handler: Option<PasteHandler<T, S>>,
499
500 overlays: Vec<Box<dyn Overlay<A = A>>>,
501 overlay_config: Option<OverlayConfig>,
502 previewer: Option<Either<Preview, Previewer>>,
503
504 hidden_columns: Vec<bool>,
505
506 initializer: Option<Initializer<T, S>>,
508 pub channel: Option<(
509 RenderSender<A>,
510 tokio::sync::mpsc::UnboundedReceiver<crate::message::RenderCommand<A>>,
511 )>,
512 }
513
514 impl<'a, T: SSS, S: Selection, A: ActionExt> PickOptions<'a, T, S, A> {
515 pub const fn new() -> Self {
516 Self {
517 matcher: None,
518 event_loop: None,
519 previewer: None,
520 binds: None,
521 matcher_config: nucleo::Config::DEFAULT,
522 ext_handler: None,
523 ext_aliaser: None,
524 #[cfg(feature = "bracketed-paste")]
525 paste_handler: None,
526 overlay_config: None,
527 overlays: Vec::new(),
528 channel: None,
529 hidden_columns: Vec::new(),
530 initializer: None,
531 }
532 }
533
534 pub fn with_binds(binds: BindMap<A>) -> Self {
535 let mut ret = Self::new();
536 ret.binds = Some(binds);
537 ret
538 }
539
540 pub fn with_matcher(matcher: &'a mut nucleo::Matcher) -> Self {
541 let mut ret = Self::new();
542 ret.matcher = Some(matcher);
543 ret
544 }
545
546 pub fn binds(mut self, binds: BindMap<A>) -> Self {
547 self.binds = Some(binds);
548 self
549 }
550
551 pub fn event_loop(mut self, event_loop: EventLoop<A>) -> Self {
552 self.event_loop = Some(event_loop);
553 self
554 }
555
556 pub fn previewer(mut self, previewer: Previewer) -> Self {
560 self.previewer = Some(Either::Right(previewer));
561 self
562 }
563
564 pub fn preview(mut self, preview: Preview) -> Self {
567 self.previewer = Some(Either::Left(preview));
568 self
569 }
570
571 pub fn matcher(mut self, matcher_config: nucleo::Config) -> Self {
572 self.matcher_config = matcher_config;
573 self
574 }
575
576 pub fn hidden_columns(mut self, hidden_columns: Vec<bool>) -> Self {
577 self.hidden_columns = hidden_columns;
578 self
579 }
580
581 pub fn ext_handler<F>(mut self, handler: F) -> Self
582 where
583 F: FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static,
584 {
585 self.ext_handler = Some(Box::new(handler));
586 self
587 }
588
589 pub fn ext_aliaser<F>(mut self, aliaser: F) -> Self
590 where
591 F: FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A> + Send + Sync + 'static,
592 {
593 self.ext_aliaser = Some(Box::new(aliaser));
594 self
595 }
596
597 pub fn initializer<F>(mut self, aliaser: F) -> Self
598 where
599 F: FnOnce(&mut MMState<'_, '_, T, S>) + Send + Sync + 'static,
600 {
601 self.initializer = Some(Box::new(aliaser));
602 self
603 }
604
605 #[cfg(feature = "bracketed-paste")]
606 pub fn paste_handler<F>(mut self, handler: F) -> Self
607 where
608 F: FnMut(String, &MMState<'_, '_, T, S>) -> String + Send + Sync + 'static,
609 {
610 self.paste_handler = Some(Box::new(handler));
611 self
612 }
613
614 pub fn overlay<O>(mut self, overlay: O) -> Self
615 where
616 O: Overlay<A = A> + 'static,
617 {
618 self.overlays.push(Box::new(overlay));
619 self
620 }
621
622 pub fn overlay_config(mut self, overlay: OverlayConfig) -> Self {
623 self.overlay_config = Some(overlay);
624 self
625 }
626
627 pub fn render_tx(&mut self) -> RenderSender<A> {
628 if let Some((s, _)) = &self.channel {
629 s.clone()
630 } else {
631 let channel = tokio::sync::mpsc::unbounded_channel();
632 let ret = channel.0.clone();
633 self.channel = Some(channel);
634 ret
635 }
636 }
637 }
638
639 impl<'a, T: SSS, S: Selection, A: ActionExt> Default for PickOptions<'a, T, S, A> {
640 fn default() -> Self {
641 Self::new()
642 }
643 }
644
645 pub type AttachmentFormatter<T, S> = Either<
648 Arc<RenderFn<T>>,
649 for<'a, 'b, 'c> fn(&'a MMState<'b, 'c, T, S>, &'a str, Option<&dyn Fn(String)>) -> String,
650 >;
651
652 pub fn use_formatter<T: SSS, S: Selection>(
653 formatter: &AttachmentFormatter<T, S>,
654 state: &MMState<'_, '_, T, S>,
655 template: &str,
656 repeat: Option<&dyn Fn(String)>,
657 ) -> String {
658 if template.is_empty() {
659 return String::new();
660 }
661 match formatter {
662 Either::Left(f) => {
663 if let Some(t) = state.current_raw() {
664 f(t, template)
665 } else {
666 String::new()
667 }
668 }
669 Either::Right(f) => f(state, template, repeat),
670 }
671 }
672
673 impl<T: SSS, S: Selection + 'static> Matchmaker<T, S> {
675 pub fn register_print_handler(
678 &mut self,
679 print_handle: AppendOnly<String>,
680 output_separator: String,
681 formatter: AttachmentFormatter<T, S>,
682 ) {
683 self.register_interrupt_handler(Interrupt::Print, move |state| {
684 let template = state.payload().clone();
685 let repeat = |s: String| {
686 if atty::is(atty::Stream::Stdout) {
687 print_handle.push(s);
688 } else {
689 print!("{}{}", s, output_separator);
690 }
691 };
692 let s = use_formatter(&formatter, state, &template, Some(&repeat));
693 if !s.is_empty() {
694 repeat(s)
695 }
696 });
697 }
698
699 pub fn register_execute_handler(&mut self, formatter: AttachmentFormatter<T, S>) {
704 let _formatter = formatter.clone();
705 self.register_interrupt_handler(Interrupt::Execute, move |state| {
706 let template = state.payload().clone();
707 if !template.is_empty() {
708 let cmd = use_formatter(&formatter, state, &template, None);
709 if cmd.is_empty() {
710 return;
711 }
712 let mut vars = state.make_env_vars();
713
714 let preview_template = state.preview_payload().clone();
715 let preview_cmd = use_formatter(&formatter, state, &preview_template, None);
716 let extra = env_vars!(
717 "MM_PREVIEW_COMMAND" => preview_cmd,
718 );
719 vars.extend(extra);
720
721 if let Some(mut child) = Command::from_script(&cmd)
722 .envs(vars)
723 .stdin(maybe_tty())
724 ._spawn()
725 {
726 match child.wait() {
727 Ok(i) => {
728 info!("Command [{cmd}] exited with {i}")
729 }
730 Err(e) => {
731 info!("Failed to wait on command [{cmd}]: {e}")
732 }
733 }
734 }
735 };
736 });
737 self.register_interrupt_handler(Interrupt::ExecuteSilent, move |state| {
738 let template = state.payload().clone();
739 if !template.is_empty() {
740 let cmd = use_formatter(&_formatter, state, &template, None);
741 if cmd.is_empty() {
742 return;
743 }
744 let mut vars = state.make_env_vars();
745
746 let preview_template = state.preview_payload().clone();
747 let preview_cmd = use_formatter(&_formatter, state, &preview_template, None);
748 let extra = env_vars!(
749 "MM_PREVIEW_COMMAND" => preview_cmd,
750 );
751 vars.extend(extra);
752
753 if let Some(mut child) = Command::from_script(&cmd)
754 .envs(vars)
755 .stdin(maybe_tty())
756 ._spawn()
757 {
758 match child.wait() {
759 Ok(i) => {
760 info!("Command [{cmd}] exited with {i}")
761 }
762 Err(e) => {
763 info!("Failed to wait on command [{cmd}]: {e}")
764 }
765 }
766 }
767 };
768 });
769 }
770
771 pub fn register_become_handler(&mut self, formatter: AttachmentFormatter<T, S>) {
776 self.register_interrupt_handler(Interrupt::Become, move |state| {
777 let template = state.payload().clone();
778 if !template.is_empty() {
779 let cmd = use_formatter(&formatter, state, &template, None);
780 if cmd.is_empty() {
781 return;
782 }
783 let mut vars = state.make_env_vars();
784
785 let preview_template = state.preview_payload().clone();
786 let preview_cmd = use_formatter(&formatter, state, &preview_template, None);
787 let extra = env_vars!(
788 "MM_PREVIEW_COMMAND" => preview_cmd,
789 );
790 vars.extend(extra);
791 debug!("Becoming: {cmd}");
792
793 Command::from_script(&cmd).envs(vars)._exec()
794 }
795 });
796 }
797 }
798
799 pub fn make_previewer<T: SSS, S: Selection + 'static>(
802 mm: &mut Matchmaker<T, S>,
803 previewer_config: PreviewerConfig, formatter: AttachmentFormatter<T, S>,
805 help_str: Text<'static>,
806 ) -> Previewer {
807 let (previewer, tx) = Previewer::new(previewer_config);
809 let preview_tx = tx.clone();
810
811 mm.register_event_handler(Event::CursorChange | Event::PreviewChange, move |state, _| {
813 if state.preview_visible() &&
814 let m = state.preview_payload().clone() &&
815 !m.is_empty()
816 {
817 let cmd = use_formatter(&formatter, state, &m, None);
818 if cmd.is_empty() {
819 return;
820 }
821 let mut envs = state.make_env_vars();
822 let extra = env_vars!(
823 "COLUMNS" => state.previewer_area().map_or("0".to_string(), |r| r.width.to_string()),
824 "LINES" => state.previewer_area().map_or("0".to_string(), |r| r.height.to_string()),
825 );
826 envs.extend(extra);
827
828 let msg = PreviewMessage::Run(cmd.clone(), envs);
829 if preview_tx.send(msg.clone()).is_err() {
830 warn!("Failed to send to preview: {}", msg)
831 }
832
833 let target = state.preview_ui.as_ref().and_then(|p| p.config.initial.index.as_ref().and_then(|index_col| {
834 state.current_raw().and_then(|item| {
835 state.picker_ui.worker.format_with(item, index_col).and_then(|t| atoi::atoi(t.as_bytes()))
836 })
837 }));
838
839 if let Some(p) = state.preview_ui {
840 p.set_target(target);
841 };
842
843 } else if preview_tx.send(PreviewMessage::Stop).is_err() {
844 warn!("Failed to send to preview: stop")
845 }
846
847 state.preview_set_payload = None;
848 }
849 );
850
851 mm.register_event_handler(Event::PreviewSet, move |state, _event| {
852 if state.preview_visible() {
853 let msg = if let Some(m) = state.preview_set_payload() {
854 let m = if m.is_empty() && !help_str.lines.is_empty() {
855 help_str.clone()
856 } else {
857 Text::from(m)
858 };
859 PreviewMessage::Set(m)
860 } else {
861 PreviewMessage::Unset
862 };
863
864 if tx.send(msg.clone()).is_err() {
865 warn!("Failed to send: {}", msg)
866 }
867 }
868 });
869
870 previewer
871}
872
873fn maybe_tty() -> Stdio {
874 if let Ok(tty) = std::fs::File::open("/dev/tty") {
875 Stdio::from(tty)
877 } else {
878 log::error!("Failed to open /dev/tty");
879 Stdio::inherit()
880 }
881}
882
883impl<T: SSS + Debug, S: Selection + Debug> Debug for Matchmaker<T, S> {
886 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
887 f.debug_struct("Matchmaker")
888 .field("render_config", &self.render_config)
890 .field("tui_config", &self.tui_config)
891 .field("selection_set", &self.selector)
892 .field("event_handlers", &self.event_handlers)
893 .field("interrupt_handlers", &self.interrupt_handlers)
894 .finish()
895 }
896}