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<
65 SegmentedInjector<
66 Either<String, Text<'static>>,
67 IndexedInjector<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.0 == 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
163 capture_to_idx.iter().enumerate().skip(1)
164 {
165 if let Some(col_idx) = col_idx_opt {
166 if let Some(m) = caps.get(group_idx) {
167 ranges[*col_idx] = (m.start(), m.end());
168 }
169 }
170 }
171 }
172
173 ranges
174 })
175 } else if has_unnamed {
176 log::debug!("Unnamed regex: {rg} with {} groups", capture_to_idx.len());
177
178 Arc::new(move |s| {
180 let s = &s.to_cow();
181 let mut ranges = ArrayVec::from_iter(vec![(0, 0); col_count]);
182
183 if let Some(caps) = rg.captures(s) {
184 for (i, group) in caps.iter().skip(1).enumerate().take(col_count) {
185 if let Some(m) = group {
186 ranges[i] = (m.start(), m.end());
187 }
188 }
189 }
190
191 ranges
192 })
193 } else {
194 log::debug!("Delimiter regex: {rg}");
195
196 Arc::new(move |s| {
198 let s = &s.to_cow();
199 let mut ranges = ArrayVec::new();
200 let mut last_end = 0;
201
202 for m in rg.find_iter(s).take(col_count - 1) {
203 ranges.push((last_end, m.start()));
204 last_end = m.end();
205 }
206
207 ranges.push((last_end, s.len()));
208 ranges
209 })
210 }
211 }
212 Split::Regexes(ref rgs) => {
214 let rgs = rgs.clone(); Arc::new(move |s| {
216 let s = &s.to_cow();
217 let mut ranges = ArrayVec::new();
218
219 for re in rgs.iter().take(col_count) {
220 if let Some(m) = re.find(s) {
221 ranges.push((m.start(), m.end()));
222 } else {
223 ranges.push((0, 0));
224 }
225 }
226 ranges
227 })
228 }
229 Split::None => Arc::new(|s| ArrayVec::from_iter([(0, s.to_cow().len())])),
230 };
231 let injector = IndexedInjector::new_globally_indexed(injector);
232 let injector = SegmentedInjector::new(injector, splitter.clone());
233 let injector = AnsiInjector::new(injector, preprocess_config);
234
235 let selection_set = if render_config.results.multi {
236 Selector::new(Indexed::identifier)
237 } else {
238 Selector::new(Indexed::identifier).disabled()
239 };
240
241 let event_handlers = EventHandlers::new();
242 let interrupt_handlers = InterruptHandlers::new();
243
244 let new = Matchmaker {
245 worker,
246 render_config,
247 tui_config,
248 exit_config,
249 selector: selection_set,
250 event_handlers,
251 interrupt_handlers,
252 };
253
254 let misc = OddEnds {
255 splitter,
256 hidden_columns,
257 has_error,
258 };
259
260 (new, injector, misc)
261 }
262}
263
264impl<T: SSS, S: Selection> Matchmaker<T, S> {
265 pub fn new(worker: Worker<T>, selector: Selector<T, S>) -> Self {
266 Matchmaker {
267 worker,
268 render_config: RenderConfig::default(),
269 tui_config: TerminalConfig::default(),
270 exit_config: ExitConfig::default(),
271 selector,
272 event_handlers: EventHandlers::new(),
273 interrupt_handlers: InterruptHandlers::new(),
274 }
275 }
276
277 pub fn config_render(&mut self, render: RenderConfig) -> &mut Self {
279 self.render_config = render;
280 self
281 }
282 pub fn config_tui(&mut self, tui: TerminalConfig) -> &mut Self {
284 self.tui_config = tui;
285 self
286 }
287 pub fn config_exit(&mut self, exit: ExitConfig) -> &mut Self {
289 self.exit_config = exit;
290 self
291 }
292 pub fn register_event_handler<F>(&mut self, event: Event, handler: F)
294 where
295 F: Fn(&mut MMState<'_, '_, T, S>, &Event) + 'static,
296 {
297 let boxed = Box::new(handler);
298 self.register_boxed_event_handler(event, boxed);
299 }
300 pub fn register_boxed_event_handler(
302 &mut self,
303 event: Event,
304 handler: DynamicMethod<T, S, Event>,
305 ) {
306 self.event_handlers.set(event, handler);
307 }
308 pub fn register_interrupt_handler<F>(&mut self, interrupt: Interrupt, handler: F)
310 where
311 F: Fn(&mut MMState<'_, '_, T, S>) + 'static,
312 {
313 let boxed = Box::new(handler);
314 self.register_boxed_interrupt_handler(interrupt, boxed);
315 }
316 pub fn register_boxed_interrupt_handler(
318 &mut self,
319 variant: Interrupt,
320 handler: BoxedHandler<T, S>,
321 ) {
322 self.interrupt_handlers.set(variant, handler);
323 }
324
325 pub async fn pick<A: ActionExt>(self, builder: PickOptions<'_, T, S, A>) -> Result<Vec<S>> {
327 let PickOptions {
328 previewer,
329 ext_handler,
330 ext_aliaser,
331 #[cfg(feature = "bracketed-paste")]
332 paste_handler,
333 overlay_config,
334 hidden_columns,
335 initializer,
336 ..
337 } = builder;
338
339 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
340 return Ok(self
341 .selector
342 .identify_to_vec([self.worker.get_nth(0).unwrap()]));
343 }
344
345 let mut event_loop = if let Some(e) = builder.event_loop {
346 e
347 } else if let Some(binds) = builder.binds {
348 EventLoop::with_binds(binds).with_tick_rate(self.render_config.tick_rate())
349 } else {
350 EventLoop::new()
351 };
352
353 let mut wait = false;
354 if let Some(path) = self.exit_config.last_key_path.clone()
355 && !path.is_empty()
356 {
357 event_loop.record_last_key(path);
358 wait = true;
359 }
360
361 let preview = match previewer {
362 Some(Either::Left(view)) => Some(view),
363 Some(Either::Right(mut previewer)) => {
364 let view = previewer.view();
365 previewer.connect_controller(event_loop.controller());
366
367 tokio::spawn(async move {
368 let _ = previewer.run().await;
369 });
370
371 Some(view)
372 }
373 _ => None,
374 };
375
376 let (render_tx, render_rx) = builder
377 .channel
378 .unwrap_or_else(tokio::sync::mpsc::unbounded_channel);
379 event_loop.add_tx(render_tx.clone());
380
381 let mut tui =
382 tui::Tui::new(self.tui_config).map_err(|e| MatchError::TUIError(e.to_string()))?;
383 tui.enter()
384 .map_err(|e| MatchError::TUIError(e.to_string()))?;
385
386 let event_controller = event_loop.controller();
388 let event_loop_handle = tokio::spawn(async move {
389 let _ = event_loop.run().await;
390 });
391 log::debug!("event loop started");
392
393 let overlay_ui = if builder.overlays.is_empty() {
394 None
395 } else {
396 Some(OverlayUI::new(
397 builder.overlays.into_boxed_slice(),
398 overlay_config.unwrap_or_default(),
399 ))
400 };
401
402 tui.redraw();
404
405 let matcher = if let Some(matcher) = builder.matcher {
406 matcher
407 } else {
408 &mut nucleo::Matcher::new(nucleo::Config::DEFAULT)
409 };
410
411 let (ui, picker, footer, preview) = UI::new(
412 self.render_config,
413 matcher,
414 self.worker,
415 self.selector,
416 preview,
417 &mut tui,
418 hidden_columns,
419 );
420
421 let ret = render::render_loop(
422 ui,
423 picker,
424 footer,
425 preview,
426 tui,
427 overlay_ui,
428 self.exit_config,
429 render_rx,
430 event_controller,
431 (self.event_handlers, self.interrupt_handlers),
432 ext_handler,
433 ext_aliaser,
434 initializer,
435 #[cfg(feature = "bracketed-paste")]
436 paste_handler,
437 )
438 .await;
439
440 if wait {
441 let _ = event_loop_handle.await;
442 log::debug!("event loop finished");
443 }
444
445 ret
446 }
447
448 pub async fn pick_default(self) -> Result<Vec<S>> {
449 self.pick::<NullActionExt>(PickOptions::new()).await
450 }
451}
452
453#[ext(MatchResultExt)]
454impl<T> Result<T> {
455 pub fn first<S>(self) -> Result<S>
457 where
458 T: IntoIterator<Item = S>,
459 {
460 match self {
461 Ok(v) => v.into_iter().next().ok_or(MatchError::NoMatch),
462 Err(e) => Err(e),
463 }
464 }
465
466 pub fn abort(self) -> Result<T> {
468 match self {
469 Err(MatchError::Abort(x)) => std::process::exit(x),
470 _ => self,
471 }
472 }
473}
474
475pub type PasteHandler<T, S> =
479 Box<dyn FnMut(String, &MMState<'_, '_, T, S>) -> String + Send + Sync + 'static>;
480
481pub type ActionExtHandler<T, S, A> =
482 Box<dyn FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static>;
483
484pub type ActionAliaser<T, S, A> =
485 Box<dyn FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A> + Send + Sync + 'static>;
486
487pub type Initializer<T, S> = Box<dyn FnOnce(&mut MMState<'_, '_, T, S>) + Send + Sync + 'static>;
488
489pub struct PickOptions<'a, T: SSS, S: Selection, A: ActionExt = NullActionExt> {
491 matcher: Option<&'a mut nucleo::Matcher>,
492 matcher_config: nucleo::Config,
493
494 event_loop: Option<EventLoop<A>>,
495 binds: Option<BindMap<A>>,
496
497 ext_handler: Option<ActionExtHandler<T, S, A>>,
498 ext_aliaser: Option<ActionAliaser<T, S, A>>,
499 #[cfg(feature = "bracketed-paste")]
500 paste_handler: Option<PasteHandler<T, S>>,
501
502 overlays: Vec<Box<dyn Overlay<A = A>>>,
503 overlay_config: Option<OverlayConfig>,
504 previewer: Option<Either<Preview, Previewer>>,
505
506 hidden_columns: Vec<bool>,
507
508 initializer: Option<Initializer<T, S>>,
510 pub channel: Option<(
511 RenderSender<A>,
512 tokio::sync::mpsc::UnboundedReceiver<crate::message::RenderCommand<A>>,
513 )>,
514}
515
516impl<'a, T: SSS, S: Selection, A: ActionExt> PickOptions<'a, T, S, A> {
517 pub const fn new() -> Self {
518 Self {
519 matcher: None,
520 event_loop: None,
521 previewer: None,
522 binds: None,
523 matcher_config: nucleo::Config::DEFAULT,
524 ext_handler: None,
525 ext_aliaser: None,
526 #[cfg(feature = "bracketed-paste")]
527 paste_handler: None,
528 overlay_config: None,
529 overlays: Vec::new(),
530 channel: None,
531 hidden_columns: Vec::new(),
532 initializer: None,
533 }
534 }
535
536 pub fn with_binds(binds: BindMap<A>) -> Self {
537 let mut ret = Self::new();
538 ret.binds = Some(binds);
539 ret
540 }
541
542 pub fn with_matcher(matcher: &'a mut nucleo::Matcher) -> Self {
543 let mut ret = Self::new();
544 ret.matcher = Some(matcher);
545 ret
546 }
547
548 pub fn binds(mut self, binds: BindMap<A>) -> Self {
549 self.binds = Some(binds);
550 self
551 }
552
553 pub fn event_loop(mut self, event_loop: EventLoop<A>) -> Self {
554 self.event_loop = Some(event_loop);
555 self
556 }
557
558 pub fn previewer(mut self, previewer: Previewer) -> Self {
562 self.previewer = Some(Either::Right(previewer));
563 self
564 }
565
566 pub fn preview(mut self, preview: Preview) -> Self {
569 self.previewer = Some(Either::Left(preview));
570 self
571 }
572
573 pub fn matcher(mut self, matcher_config: nucleo::Config) -> Self {
574 self.matcher_config = matcher_config;
575 self
576 }
577
578 pub fn hidden_columns(mut self, hidden_columns: Vec<bool>) -> Self {
579 self.hidden_columns = hidden_columns;
580 self
581 }
582
583 pub fn ext_handler<F>(mut self, handler: F) -> Self
584 where
585 F: FnMut(A, &mut MMState<'_, '_, T, S>) + Send + Sync + 'static,
586 {
587 self.ext_handler = Some(Box::new(handler));
588 self
589 }
590
591 pub fn ext_aliaser<F>(mut self, aliaser: F) -> Self
592 where
593 F: FnMut(Action<A>, &mut MMState<'_, '_, T, S>) -> Actions<A> + Send + Sync + 'static,
594 {
595 self.ext_aliaser = Some(Box::new(aliaser));
596 self
597 }
598
599 pub fn initializer<F>(mut self, aliaser: F) -> Self
600 where
601 F: FnOnce(&mut MMState<'_, '_, T, S>) + Send + Sync + 'static,
602 {
603 self.initializer = Some(Box::new(aliaser));
604 self
605 }
606
607 #[cfg(feature = "bracketed-paste")]
608 pub fn paste_handler<F>(mut self, handler: F) -> Self
609 where
610 F: FnMut(String, &MMState<'_, '_, T, S>) -> String + Send + Sync + 'static,
611 {
612 self.paste_handler = Some(Box::new(handler));
613 self
614 }
615
616 pub fn overlay<O>(mut self, overlay: O) -> Self
617 where
618 O: Overlay<A = A> + 'static,
619 {
620 self.overlays.push(Box::new(overlay));
621 self
622 }
623
624 pub fn overlay_config(mut self, overlay: OverlayConfig) -> Self {
625 self.overlay_config = Some(overlay);
626 self
627 }
628
629 pub fn render_tx(&mut self) -> RenderSender<A> {
630 if let Some((s, _)) = &self.channel {
631 s.clone()
632 } else {
633 let channel = tokio::sync::mpsc::unbounded_channel();
634 let ret = channel.0.clone();
635 self.channel = Some(channel);
636 ret
637 }
638 }
639}
640
641impl<'a, T: SSS, S: Selection, A: ActionExt> Default for PickOptions<'a, T, S, A> {
642 fn default() -> Self {
643 Self::new()
644 }
645}
646
647pub type AttachmentFormatter<T, S> = Either<
650 Arc<RenderFn<T>>,
651 for<'a, 'b, 'c> fn(&'a MMState<'b, 'c, T, S>, &'a str, Option<&dyn Fn(String)>) -> String,
652>;
653
654pub fn use_formatter<T: SSS, S: Selection>(
656 formatter: &AttachmentFormatter<T, S>,
657 state: &MMState<'_, '_, T, S>,
658 template: &str,
659 repeat: Option<&dyn Fn(String)>,
660) -> String {
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
673impl<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
799pub 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 | Event::Synced, move |state, _| {
813 if state.preview_visible() &&
814 let m = state.preview_payload().clone()
815 {
816 let cmd = use_formatter(&formatter, state, &m, None);
817 if cmd.is_empty() {
818 return;
819 }
820 let mut envs = state.make_env_vars();
821 let extra = env_vars!(
822 "COLUMNS" => state.previewer_area().map_or("0".to_string(), |r| r.width.to_string()),
823 "LINES" => state.previewer_area().map_or("0".to_string(), |r| r.height.to_string()),
824 );
825 envs.extend(extra);
826
827 let msg = PreviewMessage::Run(cmd.clone(), envs);
828 if preview_tx.send(msg.clone()).is_err() {
829 warn!("Failed to send to preview: {}", msg)
830 }
831
832 let target = state.preview_ui.as_ref().and_then(|p| p.config.initial.index.as_ref().and_then(|index_col| {
833 state.current_raw().and_then(|item| {
834 state.picker_ui.worker.format_with(item, index_col).and_then(|t| atoi::atoi(t.as_bytes()))
835 })
836 }));
837
838 if let Some(p) = state.preview_ui {
839 p.set_target(target);
840 };
841
842 } else if preview_tx.send(PreviewMessage::Stop).is_err() {
843 warn!("Failed to send to preview: stop")
844 }
845
846 state.preview_set_payload = None;
847 }
848 );
849
850 mm.register_event_handler(Event::PreviewSet, move |state, _event| {
851 if state.preview_visible() {
852 let msg = if let Some(m) = state.preview_set_payload() {
853 let m = if m.is_empty() && !help_str.lines.is_empty() {
854 help_str.clone()
855 } else {
856 Text::from(m)
857 };
858 PreviewMessage::Set(m)
859 } else {
860 PreviewMessage::Unset
861 };
862
863 if tx.send(msg.clone()).is_err() {
864 warn!("Failed to send: {}", msg)
865 }
866 }
867 });
868
869 previewer
870}
871
872fn maybe_tty() -> Stdio {
873 if let Ok(tty) = std::fs::File::open("/dev/tty") {
874 Stdio::from(tty)
876 } else {
877 log::error!("Failed to open /dev/tty");
878 Stdio::inherit()
879 }
880}
881
882impl<T: SSS + Debug, S: Selection + Debug> Debug for Matchmaker<T, S> {
885 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
886 f.debug_struct("Matchmaker")
887 .field("render_config", &self.render_config)
889 .field("tui_config", &self.tui_config)
890 .field("selection_set", &self.selector)
891 .field("event_handlers", &self.event_handlers)
892 .field("interrupt_handlers", &self.interrupt_handlers)
893 .finish()
894 }
895}