1
2use std::{fmt::{self, Debug, Formatter}, process::Stdio, sync::Arc};
3
4use anyhow::bail;
5use log::{debug, error, info, warn};
6use tokio::sync::{watch::Sender};
7
8use crate::{
9 PickerItem, Result, Selection, SelectionSet, binds::BindMap, config::{
10 self,
11 ExitConfig,
12 PreviewerConfig,
13 RenderConfig,
14 Split,
15 TerminalConfig,
16 }, env_vars, event::{self}, message::{Interrupt, Event}, nucleo::{
17 injector::{
18 Indexed, IndexedInjector, Injector, Segmented, SegmentedInjector, WorkerInjector
19 },
20 worker::Worker,
21 }, render::{
22 self,
23 DynamicMethod,
24 EphemeralState,
25 EventHandlers,
26 InterruptHandlers,
27 }, spawn::{
28 exec, preview::{PreviewMessage, Previewer}, spawn, tty_or_null
29 }, tui::{self, map_chunks, map_reader_lines, read_to_chunks}, ui::UI
30};
31
32pub struct Matchmaker<T: PickerItem, S: Selection=T, C=()> {
33 pub matcher: Option<nucleo::Matcher>,
34 pub worker: Worker<T, C>,
35 render_config: RenderConfig,
36 bind_config: BindMap,
37 #[allow(dead_code)]
38 tui_config: TerminalConfig,
39 exit_config: ExitConfig,
40 selection_set: SelectionSet<T, S>,
41 context: Arc<C>,
42 event_handlers: EventHandlers<T, S, C>,
43 interrupt_handlers: InterruptHandlers<T, S, C>,
44 previewer: Option<Previewer>
45}
46
47
48pub struct MiscData {
52 pub formatter: Arc<Box<dyn Fn(&Indexed<Segmented<String>>, &str) -> String + Send + Sync>>,
53 pub splitter: Arc<dyn Fn(&String) -> Vec<(usize, usize)> + Send + Sync>
54}
55
56impl Matchmaker<Indexed<Segmented<String>>, Segmented<String>> {
57 pub fn new_from_config(config: config::Config) -> (Self, SegmentedInjector<String, IndexedInjector<Segmented<String>, WorkerInjector<Indexed<Segmented<String>>>>>, MiscData) {
58 let cc = config.matcher.columns;
59
60 let worker: Worker<Indexed<Segmented<String>>> = match cc.split {
61 Split::Delimiter(_) | Split::Regexes(_) => {
62 let names: Vec<Arc<str>> = if cc.names.is_empty() {
63 (0..cc.max_columns.0)
64 .map(|n| Arc::from(n.to_string()))
65 .collect()
66 } else {
67 cc.names.iter().map(|s| Arc::from(s.name.as_str())).collect()
68 };
69 Worker::new_indexable(names)
70 },
71 Split::None => {
72 Worker::new_indexable([""])
73 }
74 };
75
76 let injector = worker.injector();
77
78 let col_count = worker.columns.len();
79
80 let splitter: Arc<dyn Fn(&String) -> Vec<(usize, usize)> + Send + Sync> = match cc.split {
82 Split::Delimiter(ref rg) => {
83 let rg = rg.clone();
84 Arc::new(move |s| {
85 let mut ranges = Vec::new();
86 let mut last_end = 0;
87 for (i, m) in rg.find_iter(s).enumerate() {
88 if i >= col_count - 1 { break; }
89 ranges.push((last_end, m.start()));
90 last_end = m.end();
91 }
92 ranges.push((last_end, s.len()));
93 ranges
94 })
95 }
96 Split::Regexes(ref rgs) => {
97 let rgs = rgs.clone(); Arc::new(move |s| {
99 let mut ranges = Vec::new();
100 for re in rgs.iter().take(col_count) {
101 if let Some(m) = re.find(s) {
102 ranges.push((m.start(), m.end()));
103 } else {
104 ranges.push((0, 0));
105 }
106 }
107 ranges
108 })
109 }
110 Split::None => Arc::new(|s| vec![(0, s.len())]),
111 };
112 let injector= IndexedInjector::new(injector, ());
113 let injector= SegmentedInjector::new(injector, splitter.clone());
114
115 let selection_set = SelectionSet::new(Indexed::identifier);
116
117 let event_handlers = EventHandlers::new();
118 let interrupt_handlers = InterruptHandlers::new();
119 let formatter = Arc::new(worker.make_format_fn::<true>(|item| &item.inner.inner));
120
121 let (previewer, tx) = Previewer::new(config.previewer);
122
123 let mut new: Matchmaker<Indexed<Segmented<String>>, Segmented<String>> = Matchmaker {
124 matcher: Some(nucleo::Matcher::new(config.matcher.matcher.0)),
125 worker,
126 bind_config: config.binds,
127 render_config: config.render,
128 tui_config: config.tui,
129 exit_config: config.matcher.exit,
130 selection_set,
131 context: Arc::new(()),
132 event_handlers,
133 interrupt_handlers,
134 previewer: Some(previewer)
135 };
136
137 let preview_formatter = formatter.clone();
139 let execute_formatter = preview_formatter.clone();
140 let execute_preview_formatter = preview_formatter.clone();
141 let become_formatter = preview_formatter.clone();
142 let become_preview_formatter = preview_formatter.clone();
143 let reload_formatter = preview_formatter.clone();
144
145 new.register_event_handler([Event::CursorChange, Event::PreviewChange], move |state, event| {
146 match event {
147 Event::CursorChange | Event::PreviewChange => {
148 if state.preview_show &&
149 let Some(t) = state.current_raw() &&
150 !state.preview_payload().is_empty()
151 {
152 let cmd = preview_formatter.clone()(t, &state.preview_payload());
153 let mut envs = state.make_env_vars();
154 let extra = env_vars!(
155 "COLUMNS" => state.previewer_area.map_or("0".to_string(), |r| r.width.to_string()),
156 "LINES" => state.previewer_area.map_or("0".to_string(), |r| r.height.to_string()),
157 );
158 envs.extend(extra);
159
160 let msg = PreviewMessage::Run(cmd.clone(), vec![]);
161 if tx.send(msg.clone()).is_err() {
162 warn!("Failed to send: {}", msg)
163 }
164 }
165 },
166 _ => {}
167 }
168 });
169
170 new.register_interrupt_handler(Interrupt::Execute("".into()), move |state, interrupt| {
171 match interrupt {
172 Interrupt::Execute(template) => {
173 if let Some(t) = state.current_raw() {
174 let cmd = execute_formatter(t, template);
175 let mut vars = state.make_env_vars();
176 let preview_cmd = execute_preview_formatter(t, &state.preview_payload());
177 let extra = env_vars!(
178 "FZF_PREVIEW_COMMAND" => preview_cmd,
179 );
180 vars.extend(extra);
181 let tty = tty_or_null();
182 if let Some(mut child) = spawn(&cmd, vars, tty, Stdio::inherit(), Stdio::inherit()) {
183 match child.wait() {
184 Ok(i) => {
185 info!("Command [{cmd}] exited with {i}")
186 },
187 Err(e) => {
188 info!("Failed to wait on command [{cmd}]: {e}")
189 }
190 }
191 }
192 }
193 },
194 _ => {}
195 }
196 });
197
198 new.register_interrupt_handler(Interrupt::Become("".into()), move |state, interrupt| {
199 match interrupt {
200 Interrupt::Become(template) => {
201 if let Some(t) = state.current_raw() {
202 let cmd = become_formatter(t, template);
203 let mut vars = state.make_env_vars();
204 let preview_cmd = become_preview_formatter(t, &state.preview_payload());
205 let extra = env_vars!(
206 "FZF_PREVIEW_COMMAND" => preview_cmd,
207 );
208 vars.extend(extra);
209 debug!("Becoming: {cmd}");
210 exec(&cmd, vars);
211 }
212 },
213 _ => {}
214 }
215 });
216
217
218 let reload_splitter = splitter.clone();
219 new.register_interrupt_handler(Interrupt::Reload("".into()), move |state, interrupt| {
220 let injector = state.injector();
221 let injector= IndexedInjector::new(injector, ());
222 let injector= SegmentedInjector::new(injector, reload_splitter.clone());
223
224 match interrupt {
225 Interrupt::Reload(template) => {
226 if let Some(t) = state.current_raw() {
227 let cmd = reload_formatter(t, template);
228 let vars = state.make_env_vars();
229 debug!("Reloading: {cmd}");
234 if let Some(mut child) = spawn(&cmd, vars, Stdio::null(), Stdio::piped(), Stdio::null()) {
235 if let Some(stdout) = child.stdout.take() {
236 let _handle = if let Some(delim) = config.matcher.delimiter {
237 tokio::spawn(async move {
238 map_chunks::<true>(read_to_chunks(stdout, delim), |line| injector.push(line).map_err(|e| e.into()))
239 })
240 } else {
241 tokio::spawn(async move {
242 map_reader_lines::<true>(stdout, |line| injector.push(line).map_err(|e| e.into()))
243 })
244 };
245 } else {
246 error!("Failed to capture stdout");
247 }
248 }
249 }
250 },
251 _ => {}
252 }
253 });
254
255 let misc = MiscData {
256 formatter,
257 splitter
258 };
259
260 (new, injector, misc)
261 }
262}
263
264impl<T: PickerItem, S: Selection> Matchmaker<T, S> {
265 pub fn new(worker: Worker<T>, identifier: fn(&T) -> (u32, S)) -> Self {
266 let new = Matchmaker {
267 matcher: Some(nucleo::Matcher::new(nucleo::Config::DEFAULT)),
268 worker,
269 bind_config: BindMap::new(),
270 render_config: RenderConfig::default(),
271 tui_config: TerminalConfig::default(),
272 exit_config: ExitConfig::default(),
273 selection_set: SelectionSet::new(identifier),
274 context: Arc::new(()),
275 event_handlers: EventHandlers::new(),
276 interrupt_handlers: InterruptHandlers::new(),
277 previewer: None
278 };
279
280 new
281 }
282}
283
284impl<T: PickerItem, S: Selection, C> Matchmaker<T, S, C>
285{
286 pub fn new_raw(worker: Worker<T, C>, identifier: fn(&T) -> (u32, S), context: Arc<C>) -> Self {
292 let new = Matchmaker {
293 matcher: None,
294 worker,
295 bind_config: BindMap::new(),
296 render_config: RenderConfig::default(),
297 tui_config: TerminalConfig::default(),
298 exit_config: ExitConfig::default(),
299 selection_set: SelectionSet::new(identifier),
300 context,
301 event_handlers: EventHandlers::new(),
302 interrupt_handlers: InterruptHandlers::new(),
303 previewer: None
304 };
305
306 new
307 }
308
309 pub fn config_binds(&mut self, bind_config: BindMap) -> &mut Self {
311 self.bind_config = bind_config;
312 self
313 }
314 pub fn config_render(&mut self, render_config: RenderConfig) -> &mut Self {
315 self.render_config = render_config;
316 self
317 }
318 pub fn config_preview(&mut self, preview_config: PreviewerConfig) -> Sender<PreviewMessage> {
319 let (previewer, tx) = Previewer::new(preview_config);
320 self.previewer = Some(previewer);
321 tx
322 }
323 pub fn config_tui(&mut self, tui_config: TerminalConfig) -> &mut Self {
324 self.tui_config = tui_config;
325 self
326 }
327 pub fn config_exit(&mut self, exit_config: ExitConfig) -> &mut Self {
328 self.exit_config = exit_config;
329 self
330 }
331
332 pub fn register_event_handler<F, I>(&mut self, events: I, handler: F)
333 where
334 F: Fn(EphemeralState<'_, T, S, C>, &Event) + Send + Sync + 'static,
335 I: IntoIterator<Item = Event>,
336 {
337 let boxed = Box::new(handler);
338 self.register_boxed_event_handler(events, boxed);
339 }
340
341 pub fn register_boxed_event_handler<I>(
342 &mut self,
343 events: I,
344 handler: DynamicMethod<T, S, C, Event>,
345 )
346 where
347 I: IntoIterator<Item = Event>,
348 {
349 let events_vec: Vec<_> = events.into_iter().collect();
350 self.event_handlers.set(events_vec, handler);
351 }
352
353 pub fn register_interrupt_handler<F>(
354 &mut self,
355 interrupt: Interrupt,
356 handler: F,
357 )
358 where
359 F: Fn(EphemeralState<'_, T, S, C>, &Interrupt) + Send + Sync + 'static,
360 {
361 let boxed = Box::new(handler);
362 self.register_boxed_interrupt_handler(interrupt, boxed);
363 }
364
365 pub fn register_boxed_interrupt_handler(
366 &mut self,
367 variant: Interrupt,
368 handler: DynamicMethod<T, S, C, Interrupt>,
369 ) {
370 self.interrupt_handlers.set(variant, handler);
371 }
372
373
374 pub async fn pick(mut self) -> Result<impl IntoIterator<Item = S>> {
376 if let Some(matcher) = self.matcher.as_mut() {
377 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
378 return Ok(self.selection_set.map_to_vec([self.worker.get_nth(0).unwrap()]));
379 }
380
381 let (render_tx, render_rx) = tokio::sync::mpsc::unbounded_channel();
382 let (mut event_loop, controller_tx) = event::EventLoop::new(vec![render_tx.clone()], self.render_config.tick_rate());
383 event_loop.binds(self.bind_config);
384
385 let mut tui = tui::Tui::new(self.tui_config).expect("Failed to create TUI instance");
386 tui.enter()?;
387
388 tokio::spawn(async move {
389 let _ = event_loop.run().await;
390 });
391
392 let view = if let Some(mut previewer) = self.previewer {
393 previewer.connect_controller(controller_tx.clone());
394 let view = previewer.view();
395 tokio::spawn(async move {
396 let _ = previewer.run().await;
397 });
398
399 Some(view)
400 } else {
401 None
402 };
403
404 let (ui, picker, preview) = UI::new(self.render_config, matcher, self.worker, self.selection_set, view, &mut tui);
405
406 render::render_loop(ui, picker, preview, tui, render_rx, controller_tx, self.context, (self.event_handlers, self.interrupt_handlers), self.exit_config).await
407 } else {
408 bail!("No matcher")
409 }
410 }
411
412 pub async fn pick_with_matcher(self, matcher: &mut nucleo::Matcher) -> Result<impl IntoIterator<Item = S>> {
413 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
414 return Ok(self.selection_set.map_to_vec([self.worker.get_nth(0).unwrap()]));
415 }
416
417 let (render_tx, render_rx) = tokio::sync::mpsc::unbounded_channel();
418 let (mut event_loop, controller_tx) = event::EventLoop::new(vec![render_tx.clone()], self.render_config.tick_rate());
419 event_loop.binds(self.bind_config);
420
421 let mut tui = tui::Tui::new(self.tui_config).expect("Failed to create TUI instance");
422 tui.enter()?;
423
424 tokio::spawn(async move {
425 let _ = event_loop.run().await;
426 });
427
428 let view = if let Some(mut previewer) = self.previewer {
429 previewer.connect_controller(controller_tx.clone());
430 let view = previewer.view();
431 tokio::spawn(async move {
432 let _ = previewer.run().await;
433 });
434
435 Some(view)
436 } else {
437 None
438 };
439
440 let (ui, picker, preview) = UI::new(self.render_config, matcher, self.worker, self.selection_set, view, &mut tui);
441
442 render::render_loop(ui, picker, preview, tui, render_rx, controller_tx, self.context, (self.event_handlers, self.interrupt_handlers), self.exit_config).await
443 }
444}
445
446
447impl<T: PickerItem + Debug, S: Selection + Debug, C: Debug> Debug for Matchmaker<T, S, C> {
450 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
451 f.debug_struct("Matchmaker")
452 .field("matcher", &self.matcher)
454 .field("render_config", &self.render_config)
455 .field("bind_config", &self.bind_config)
456 .field("tui_config", &self.tui_config)
457 .field("selection_set", &self.selection_set)
458 .field("context", &self.context)
459 .field("event_handlers", &self.event_handlers)
460 .field("interrupt_handlers", &self.interrupt_handlers)
461 .field("previewer", &self.previewer)
462 .finish()
463 }
464}