1
2use std::{fmt::{self, Debug, Formatter}, sync::Arc};
3
4use tokio::sync::{mpsc::UnboundedSender};
5
6use crate::{
7 MMItem, MatchError, RenderFn, Result, Selection, SelectionSet, SplitterFn, binds::BindMap, proc::
8 Preview, config::{
9 self, ExitConfig, MMConfig, RenderConfig, Split, TerminalConfig
10 }, event::EventLoop, message::{Event, Interrupt}, nucleo::{
11 Indexed, Segmented, Worker, injector::{
12 IndexedInjector, Injector, SegmentedInjector, WorkerInjector
13 }
14 }, render::{
15 self,
16 DynamicMethod,
17 EphemeralState,
18 EventHandlers,
19 InterruptHandlers,
20 }, tui::{self}, ui::UI
21};
22
23pub struct Matchmaker<T: MMItem, S: Selection=T, C=()> {
31 pub worker: Worker<T, C>,
32 render_config: RenderConfig,
33 bind_config: BindMap,
34 tui_config: TerminalConfig,
35 exit_config: ExitConfig,
36 selection_set: SelectionSet<T, S>,
37 event_loop: EventLoop,
38 context: Arc<C>,
39 event_handlers: EventHandlers<T, S, C>,
40 interrupt_handlers: InterruptHandlers<T, S, C>,
41 previewer: Option<Preview>
42}
43
44
45pub struct OddEnds {
49 pub formatter: Arc<RenderFn<Indexed<Segmented<String>>>>,
50 pub splitter: SplitterFn<String>
51}
52
53pub type ConfigInjector = SegmentedInjector<String, IndexedInjector<Segmented<String>, WorkerInjector<Indexed<Segmented<String>>>>>;
54pub type ConfigMatchmaker = Matchmaker<Indexed<Segmented<String>>, Segmented<String>>;
55impl ConfigMatchmaker {
56 pub fn new_from_config(config: config::Config, matcher_config: MMConfig) -> (Self, ConfigInjector, OddEnds) {
58 let cc = matcher_config.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)
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: SplitterFn<String> = 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 new: Matchmaker<Indexed<Segmented<String>>, Segmented<String>> = Matchmaker {
122 worker,
123 bind_config: config.binds,
124 render_config: config.render,
125 tui_config: config.tui,
126 exit_config: matcher_config.exit,
127 selection_set,
128 context: Arc::new(()),
129 event_loop: EventLoop::new(),
130 event_handlers,
131 interrupt_handlers,
132 previewer: None
133 };
134
135 let misc = OddEnds {
136 formatter,
137 splitter
138 };
139
140 (new, injector, misc)
141 }
142}
143
144impl<T: MMItem, S: Selection> Matchmaker<T, S> {
145 pub fn new(worker: Worker<T>, identifier: fn(&T) -> (u32, S)) -> Self {
146 Matchmaker {
147 worker,
148 bind_config: BindMap::new(),
149 render_config: RenderConfig::default(),
150 tui_config: TerminalConfig::default(),
151 exit_config: ExitConfig::default(),
152 selection_set: SelectionSet::new(identifier),
153 context: Arc::new(()),
154 event_loop: EventLoop::new(),
155 event_handlers: EventHandlers::new(),
156 interrupt_handlers: InterruptHandlers::new(),
157 previewer: None
158 }
159 }
160}
161
162impl<T: MMItem, S: Selection, C> Matchmaker<T, S, C>
163{
164 pub fn new_raw(worker: Worker<T, C>, identifier: fn(&T) -> (u32, S), context: Arc<C>) -> Self {
165 Matchmaker {
166 worker,
167 bind_config: BindMap::new(),
168 render_config: RenderConfig::default(),
169 tui_config: TerminalConfig::default(),
170 exit_config: ExitConfig::default(),
171 selection_set: SelectionSet::new(identifier),
172 context,
173 event_handlers: EventHandlers::new(),
174 event_loop: EventLoop::new(),
175 interrupt_handlers: InterruptHandlers::new(),
176 previewer: None
177 }
178 }
179
180 pub fn get_controller(&self) -> UnboundedSender<Event> {
183 self.event_loop.get_controller()
184 }
185 pub fn connect_preview(&mut self, preview: Preview) {
187 self.previewer = Some(preview);
188 }
189
190 pub fn config_binds(&mut self, bind_config: BindMap) -> &mut Self {
192 self.bind_config = bind_config;
193 self
194 }
195 pub fn config_render(&mut self, render_config: RenderConfig) -> &mut Self {
197 self.render_config = render_config;
198 self
199 }
200 pub fn config_tui(&mut self, tui_config: TerminalConfig) -> &mut Self {
202 self.tui_config = tui_config;
203 self
204 }
205 pub fn config_exit(&mut self, exit_config: ExitConfig) -> &mut Self {
207 self.exit_config = exit_config;
208 self
209 }
210
211 pub fn register_event_handler<F, I>(&mut self, events: I, handler: F)
213 where
214 F: Fn(&mut EphemeralState<'_, T, S, C>, &Event) + Send + Sync + 'static,
215 I: IntoIterator<Item = Event>,
216 {
217 let boxed = Box::new(handler);
218 self.register_boxed_event_handler(events, boxed);
219 }
220 pub fn register_boxed_event_handler<I>(
222 &mut self,
223 events: I,
224 handler: DynamicMethod<T, S, C, Event>,
225 )
226 where
227 I: IntoIterator<Item = Event>,
228 {
229 let events_vec: Vec<_> = events.into_iter().collect();
230 self.event_handlers.set(events_vec, handler);
231 }
232 pub fn register_interrupt_handler<F>(
234 &mut self,
235 interrupt: Interrupt,
236 handler: F,
237 )
238 where
239 F: Fn(&mut EphemeralState<'_, T, S, C>, &Interrupt) + Send + Sync + 'static,
240 {
241 let boxed = Box::new(handler);
242 self.register_boxed_interrupt_handler(interrupt, boxed);
243 }
244 pub fn register_boxed_interrupt_handler(
246 &mut self,
247 variant: Interrupt,
248 handler: DynamicMethod<T, S, C, Interrupt>,
249 ) {
250 self.interrupt_handlers.set(variant, handler);
251 }
252
253 pub async fn pick_with_matcher(mut self, matcher: &mut nucleo::Matcher) -> Result<Vec<S>, MatchError> {
255 if self.exit_config.select_1 && self.worker.counts().0 == 1 {
256 return Ok(self.selection_set.map_to_vec([self.worker.get_nth(0).unwrap()]));
257 }
258
259 let (render_tx, render_rx) = tokio::sync::mpsc::unbounded_channel();
260 self.event_loop.add_tx(render_tx.clone()).set_tick_rate(self.render_config.tick_rate());
261
262 let event_controller = self.event_loop.get_controller();
263 self.event_loop.binds(self.bind_config);
264
265 let mut tui = tui::Tui::new(self.tui_config).map_err(|e| MatchError::TUIError(e.to_string()))?;
266 tui.enter().map_err(|e| MatchError::TUIError(e.to_string()))?;
267
268 tokio::spawn(async move {
269 let _ = self.event_loop.run().await;
270 });
271
272 let (ui, picker, preview) = UI::new(self.render_config, matcher, self.worker, self.selection_set, self.previewer, &mut tui);
273
274 render::render_loop(ui, picker, preview, tui, render_rx, event_controller, self.context, (self.event_handlers, self.interrupt_handlers), self.exit_config).await
275 }
276
277 pub async fn pick(self) -> Result<Vec<S>, MatchError> {
278 let mut matcher= nucleo::Matcher::new(nucleo::Config::DEFAULT);
279 self.pick_with_matcher(&mut matcher).await
280 }
281}
282impl<T: MMItem + Debug, S: Selection + Debug, C: Debug> Debug for Matchmaker<T, S, C> {
285 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
286 f.debug_struct("Matchmaker")
287 .field("render_config", &self.render_config)
289 .field("bind_config", &self.bind_config)
290 .field("tui_config", &self.tui_config)
291 .field("selection_set", &self.selection_set)
292 .field("context", &self.context)
293 .field("event_handlers", &self.event_handlers)
294 .field("interrupt_handlers", &self.interrupt_handlers)
295 .field("previewer", &self.previewer)
296 .finish()
297 }
298}