1#![allow(unused)]
5
6use std::{
7 borrow::Cow,
8 sync::{
9 Arc,
10 atomic::{self, AtomicU32},
11 },
12};
13use super::{
14 Style,
15 Line, Span, Text,
16};
17use ratatui::style::Modifier;
18use unicode_segmentation::UnicodeSegmentation;
19use unicode_width::UnicodeWidthStr;
20
21use crate::{MMItem, utils::text::{plain_text, wrap_text}};
22
23use super::{injector::WorkerInjector, query::PickerQuery};
24
25type ColumnFormatFn<T, C> = Box<dyn for<'a> Fn(&'a T, &'a C) -> Text<'a> + Send + Sync>;
26pub struct Column<T, D = ()> {
27 pub name: Arc<str>,
28 pub(super) format: ColumnFormatFn<T, D>,
29 pub(super) filter: bool,
31}
32
33impl<T, D> Column<T, D> {
34 pub fn new_boxed(name: impl Into<Arc<str>>, format: ColumnFormatFn<T, D>) -> Self {
35 Self {
36 name: name.into(),
37 format,
38 filter: true,
39 }
40 }
41
42 pub fn new<F>(name: impl Into<Arc<str>>, f: F) -> Self
43 where
44 F: for<'a> Fn(&'a T, &'a D) -> Text<'a> + Send + Sync + 'static,
45 {
46 Self {
47 name: name.into(),
48 format: Box::new(f),
49 filter: true,
50 }
51 }
52
53 pub fn without_filtering(mut self) -> Self {
54 self.filter = false;
55 self
56 }
57
58 pub(super) fn format<'a>(&self, item: &'a T, context: &'a D) -> Text<'a> {
59 (self.format)(item, context)
60 }
61
62 pub(super) fn format_text<'a>(&self, item: &'a T, context: &'a D) -> Cow<'a, str> {
63 Cow::Owned(plain_text(&(self.format)(item, context)))
64 }
65}
66
67pub struct Worker<T, C = ()>
69where
70T: MMItem,
71{
72 pub(super) nucleo: nucleo::Nucleo<T>,
74 pub(super) query: PickerQuery,
76 pub(super) col_indices_buffer: Vec<u32>,
79 pub(crate) columns: Arc<[Column<T, C>]>,
80 pub(super) context: Arc<C>,
81
82 pub(super) version: Arc<AtomicU32>,
84}
85
86impl<T, C> Worker<T, C>
87where
88T: MMItem,
89{
90 pub fn new(
91 columns: impl IntoIterator<Item = Column<T, C>>,
92 default_column: usize,
93 context: C,
94 ) -> Self {
95 let columns: Arc<[_]> = columns.into_iter().collect();
96 let matcher_columns = columns.iter().filter(|col| col.filter).count() as u32;
97
98 let inner = nucleo::Nucleo::new(
99 nucleo::Config::DEFAULT,
100 Arc::new(|| {}),
101 None,
102 matcher_columns,
103 );
104
105 Self {
106 nucleo: inner,
107 col_indices_buffer: Vec::with_capacity(128),
108 query: PickerQuery::new(columns.iter().map(|col| &col.name).cloned(), default_column),
109 columns,
110 context: Arc::new(context),
111 version: Arc::new(AtomicU32::new(0)),
112 }
113 }
114
115 pub fn injector(&self) -> WorkerInjector<T, C> {
116 WorkerInjector {
117 inner: self.nucleo.injector(),
118 columns: self.columns.clone(),
119 context: self.context.clone(),
120 version: self.version.load(atomic::Ordering::Relaxed),
121 picker_version: self.version.clone(),
122 }
123 }
124
125 pub fn find(&mut self, line: &str) {
126 let old_query = self.query.parse(line);
127 if self.query == old_query {
128 return;
129 }
130 for (i, column) in self
131 .columns
132 .iter()
133 .filter(|column| column.filter)
134 .enumerate()
135 {
136 let pattern = self
137 .query
138 .get(&column.name)
139 .map(|f| &**f)
140 .unwrap_or_default();
141 let old_pattern = old_query
142 .get(&column.name)
143 .map(|f| &**f)
144 .unwrap_or_default();
145 if pattern == old_pattern {
147 continue;
148 }
149 let is_append = pattern.starts_with(old_pattern);
150 self.nucleo.pattern.reparse(
151 i,
152 pattern,
153 nucleo::pattern::CaseMatching::Smart,
154 nucleo::pattern::Normalization::Smart,
155 is_append,
156 );
157 }
158 }
159
160 pub fn shutdown(&mut self) {
162 todo!()
163 }
164
165 pub fn restart(&mut self, clear_snapshot: bool) {
166 self.nucleo.restart(clear_snapshot);
167 }
168
169 pub fn get_nth(&self, n: u32) -> Option<&T> {
171 self.nucleo
172 .snapshot()
173 .get_matched_item(n)
174 .map(|item| item.data)
175 }
176
177 pub fn new_snapshot(nucleo: &mut nucleo::Nucleo<T>) -> (&nucleo::Snapshot<T>, Status) {
178 let nucleo::Status { changed, running } = nucleo.tick(10);
179 let snapshot = nucleo.snapshot();
180 (
181 snapshot,
182 Status {
183 item_count: snapshot.item_count(),
184 matched_count: snapshot.matched_item_count(),
185 running,
186 changed,
187 },
188 )
189 }
190
191 pub fn raw_results(&self) -> impl ExactSizeIterator<Item = &T> + DoubleEndedIterator + '_ {
192 let snapshot = self.nucleo.snapshot();
193 snapshot.matched_items(..).map(|item| item.data)
194 }
195
196 pub fn counts(&self) -> (u32, u32) {
198 let snapshot = self.nucleo.snapshot();
199 (snapshot.matched_item_count(), snapshot.item_count())
200 }
201}
202
203pub type WorkerResults<'a, T> = Vec<(Vec<Text<'a>>, &'a T, u16)>;
204
205#[derive(Debug, Default, Clone)]
206pub struct Status {
207 pub item_count: u32,
208 pub matched_count: u32,
209 pub running: bool,
210 pub changed: bool,
211}
212
213#[derive(Debug, thiserror::Error)]
214pub enum WorkerError {
215 #[error("the matcher injector has been shut down")]
216 InjectorShutdown,
217}
218
219impl<T: MMItem, C> Worker<T, C> {
220 pub fn results(
221 &mut self,
222 start: u32,
223 end: u32,
224 width_limits: &[u16],
225 highlight_style: Style,
226 matcher: &mut nucleo::Matcher,
227 ) -> (WorkerResults<'_, T>, Vec<u16>, Status) {
228
229 let (snapshot, status) = Self::new_snapshot(&mut self.nucleo);
230
231 let mut widths = vec![0u16; self.columns.len()];
232
233 let iter =
234 snapshot.matched_items(start.min(status.matched_count)..end.min(status.matched_count));
235
236 let table = iter
237 .map(|item| {
238 let mut widths = widths.iter_mut();
239 let mut col_idx = 0;
240 let mut height = 0;
241
242 let row =
243 self.columns
244 .iter()
245 .zip(width_limits.iter().chain(std::iter::repeat(&u16::MAX)))
246 .map(|(column, &width_limit)| {
247
248 let max_width = widths.next().unwrap();
249 let cell = column.format(item.data, &self.context);
250
251 if width_limit == 0 {
253 return Text::from("");
254 }
255
256 let (cell, width) = if column.filter && width_limit == u16::MAX {
257 let mut cell_width = 0;
258
259 let indices_buffer = &mut self.col_indices_buffer;
261 indices_buffer.clear();
262 snapshot.pattern().column_pattern(col_idx).indices(
263 item.matcher_columns[col_idx].slice(..),
264 matcher,
265 indices_buffer,
266 );
267 indices_buffer.sort_unstable();
268 indices_buffer.dedup();
269 let mut indices = indices_buffer.drain(..);
270
271 let mut lines = vec![];
272 let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
273 let mut grapheme_idx = 0u32;
274
275 for line in cell {
276 let mut span_list = Vec::new();
277 let mut current_span = String::new();
278 let mut current_style = Style::default();
279 let mut width = 0;
280
281 for span in line {
282 for grapheme in span.content.graphemes(true) {
288 let style = if grapheme_idx == next_highlight_idx {
289 next_highlight_idx = indices.next().unwrap_or(u32::MAX);
290 span.style.patch(highlight_style)
291 } else {
292 span.style
293 };
294 if style != current_style {
295 if !current_span.is_empty() {
296 span_list
297 .push(Span::styled(current_span, current_style))
298 }
299 current_span = String::new();
300 current_style = style;
301 }
302 current_span.push_str(grapheme);
303 grapheme_idx += 1;
304 }
305 width += span.width();
306 }
307
308 span_list.push(Span::styled(current_span, current_style));
309 lines.push(Line::from(span_list));
310 cell_width = cell_width.max(width);
311 grapheme_idx += 1; }
313
314 col_idx += 1;
315 (Text::from(lines), cell_width)
316 } else if column.filter {
317 let mut cell_width = 0;
318 let mut wrapped = false;
319
320 let indices_buffer = &mut self.col_indices_buffer;
322 indices_buffer.clear();
323 snapshot.pattern().column_pattern(col_idx).indices(
324 item.matcher_columns[col_idx].slice(..),
325 matcher,
326 indices_buffer,
327 );
328 indices_buffer.sort_unstable();
329 indices_buffer.dedup();
330 let mut indices = indices_buffer.drain(..);
331
332
333 let mut lines: Vec<Line<'_>> = vec![];
334 let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
335 let mut grapheme_idx = 0u32;
336
337 for line in cell {
338 let mut current_spans = Vec::new();
339 let mut current_span = String::new();
340 let mut current_style = Style::default();
341 let mut current_width = 0;
342
343 for span in line {
344 let mut graphemes = span.content.graphemes(true).peekable();
345 while let Some(grapheme) = graphemes.next() {
346 let grapheme_width = UnicodeWidthStr::width(grapheme);
347
348 if current_width + grapheme_width > (width_limit - 1) as usize &&
349 { grapheme_width > 1 || graphemes.peek().is_some() }
350 {
351 current_spans
352 .push(Span::styled(current_span, current_style));
353 current_spans.push(Span::styled("↵", Style::default().add_modifier(Modifier::DIM)));
354 lines.push(Line::from(current_spans));
355
356 current_spans = Vec::new();
357 current_span = String::new();
358 current_width = 0;
359 wrapped = true;
360 }
361
362 let style = if grapheme_idx == next_highlight_idx {
363 next_highlight_idx =
364 indices.next().unwrap_or(u32::MAX);
365 span.style.patch(highlight_style)
366 } else {
367 span.style
368 };
369
370 if style != current_style {
371 if !current_span.is_empty() {
372 current_spans
373 .push(Span::styled(current_span, current_style))
374 }
375 current_span = String::new();
376 current_style = style;
377 }
378 current_span.push_str(grapheme);
379 grapheme_idx += 1;
380 current_width += grapheme_width;
381 }
382 }
383
384 current_spans.push(Span::styled(current_span, current_style));
385 lines.push(Line::from(current_spans));
386 cell_width = cell_width.max(current_width);
387 grapheme_idx += 1; }
389
390 col_idx += 1;
391
392 (Text::from(lines), if wrapped { width_limit as usize } else { cell_width })
393 } else if width_limit != u16::MAX {
394 let (cell, wrapped) = wrap_text(cell, width_limit - 1);
395 let width = if wrapped { width_limit as usize } else { cell.width() };
396 (cell, width)
397 } else {
398 let width = cell.width();
399 (cell, width)
400 };
401
402 if width as u16 > *max_width {
404 *max_width = width as u16;
405 }
406
407 if cell.height() as u16 > height {
408 height = cell.height() as u16;
409 }
410
411 cell
412 });
413
414 (row.collect(), item.data, height)
415 })
416 .collect();
417
418 for (w, c) in widths.iter_mut().zip(self.columns.iter()) {
420 let name_width = c.name.width() as u16;
421 if *w != 0 {
422 *w = (*w).max(name_width);
423 }
424 }
425
426 (table, widths, status)
427 }
428}