1use super::{Line, Span, Style, Text};
5use bitflags::bitflags;
6
7use ratatui::style::Modifier;
8use std::{
9 borrow::Cow,
10 sync::{
11 Arc,
12 atomic::{self, AtomicU32},
13 },
14};
15use unicode_segmentation::UnicodeSegmentation;
16use unicode_width::UnicodeWidthStr;
17
18use super::{injector::WorkerInjector, query::PickerQuery};
19use crate::{
20 SSS,
21 nucleo::Render,
22 utils::text::{plain_text, wrap_text},
23};
24
25type ColumnFormatFn<T> = Box<dyn for<'a> Fn(&'a T) -> Text<'a> + Send + Sync>;
26pub struct Column<T> {
27 pub name: Arc<str>,
28 pub(super) format: ColumnFormatFn<T>,
29 pub(super) filter: bool,
31}
32
33impl<T> Column<T> {
34 pub fn new_boxed(name: impl Into<Arc<str>>, format: ColumnFormatFn<T>) -> 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) -> Text<'a> + SSS,
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 {
55 self.filter = false;
56 self
57 }
58
59 pub(super) fn format<'a>(&self, item: &'a T) -> Text<'a> {
60 (self.format)(item)
61 }
62
63 pub(super) fn format_text<'a>(&self, item: &'a T) -> Cow<'a, str> {
65 Cow::Owned(plain_text(&(self.format)(item)))
66 }
67}
68
69pub struct Worker<T>
73where
74 T: SSS,
75{
76 pub(crate) nucleo: nucleo::Nucleo<T>,
78 pub(super) query: PickerQuery,
80 pub(super) col_indices_buffer: Vec<u32>,
83 pub(crate) columns: Arc<[Column<T>]>,
84
85 pub(super) version: Arc<AtomicU32>,
87 column_options: Vec<ColumnOptions>,
89}
90
91bitflags! {
97 #[derive(Default, Clone, Debug)]
98 pub struct ColumnOptions: u8 {
99 const Optional = 1 << 0;
100 const OrUseDefault = 1 << 2;
101 }
102}
103
104impl<T> Worker<T>
105where
106 T: SSS,
107{
108 pub fn new(columns: impl IntoIterator<Item = Column<T>>, default_column: usize) -> Self {
110 let columns: Arc<[_]> = columns.into_iter().collect();
111 let matcher_columns = columns.iter().filter(|col| col.filter).count() as u32;
112
113 let inner = nucleo::Nucleo::new(
114 nucleo::Config::DEFAULT,
115 Arc::new(|| {}),
116 None,
117 matcher_columns,
118 );
119
120 Self {
121 nucleo: inner,
122 col_indices_buffer: Vec::with_capacity(128),
123 query: PickerQuery::new(columns.iter().map(|col| &col.name).cloned(), default_column),
124 column_options: vec![ColumnOptions::default(); columns.len()],
125 columns,
126 version: Arc::new(AtomicU32::new(0)),
127 }
128 }
129
130 #[cfg(feature = "experimental")]
131 pub fn set_column_options(&mut self, index: usize, options: ColumnOptions) {
132 if options.contains(ColumnOptions::Optional) {
133 self.nucleo
134 .pattern
135 .configure_column(index, nucleo::pattern::Variant::Optional)
136 }
137
138 self.column_options[index] = options
139 }
140
141 #[cfg(feature = "experimental")]
142 pub fn reverse_items(&mut self, reverse_items: bool) {
143 self.nucleo.reverse_items(reverse_items);
144 }
145
146 pub fn injector(&self) -> WorkerInjector<T> {
147 WorkerInjector {
148 inner: self.nucleo.injector(),
149 columns: self.columns.clone(),
150 version: self.version.load(atomic::Ordering::Relaxed),
151 picker_version: self.version.clone(),
152 }
153 }
154
155 pub fn find(&mut self, line: &str) {
156 let old_query = self.query.parse(line);
157 if self.query == old_query {
158 return;
159 }
160 for (i, column) in self
161 .columns
162 .iter()
163 .filter(|column| column.filter)
164 .enumerate()
165 {
166 let pattern = self
167 .query
168 .get(&column.name)
169 .map(|s| &**s)
170 .unwrap_or_else(|| {
171 self.column_options[i]
172 .contains(ColumnOptions::OrUseDefault)
173 .then(|| self.query.primary_column_query())
174 .flatten()
175 .unwrap_or_default()
176 });
177
178 let old_pattern = old_query
179 .get(&column.name)
180 .map(|s| &**s)
181 .unwrap_or_else(|| {
182 self.column_options[i]
183 .contains(ColumnOptions::OrUseDefault)
184 .then(|| {
185 let name = self.query.primary_column_name()?;
186 old_query.get(name).map(|s| &**s)
187 })
188 .flatten()
189 .unwrap_or_default()
190 });
191
192 if pattern == old_pattern {
194 continue;
195 }
196 let is_append = pattern.starts_with(old_pattern);
197
198 self.nucleo.pattern.reparse(
199 i,
200 pattern,
201 nucleo::pattern::CaseMatching::Smart,
202 nucleo::pattern::Normalization::Smart,
203 is_append,
204 );
205 }
206 }
207
208 pub fn get_nth(&self, n: u32) -> Option<&T> {
210 self.nucleo
211 .snapshot()
212 .get_matched_item(n)
213 .map(|item| item.data)
214 }
215
216 pub fn new_snapshot(nucleo: &mut nucleo::Nucleo<T>) -> (&nucleo::Snapshot<T>, Status) {
217 let nucleo::Status { changed, running } = nucleo.tick(10);
218 let snapshot = nucleo.snapshot();
219 (
220 snapshot,
221 Status {
222 item_count: snapshot.item_count(),
223 matched_count: snapshot.matched_item_count(),
224 running,
225 changed,
226 },
227 )
228 }
229
230 pub fn raw_results(&self) -> impl ExactSizeIterator<Item = &T> + DoubleEndedIterator + '_ {
231 let snapshot = self.nucleo.snapshot();
232 snapshot.matched_items(..).map(|item| item.data)
233 }
234
235 pub fn counts(&self) -> (u32, u32) {
237 let snapshot = self.nucleo.snapshot();
238 (snapshot.matched_item_count(), snapshot.item_count())
239 }
240
241 #[cfg(feature = "experimental")]
242 pub fn set_stability(&mut self, threshold: u32) {
243 self.nucleo.set_stability(threshold);
244 }
245
246 #[cfg(feature = "experimental")]
247 pub fn get_stability(&self) -> u32 {
248 self.nucleo.get_stability()
249 }
250
251 pub fn restart(&mut self, clear_snapshot: bool) {
252 self.nucleo.restart(clear_snapshot);
253 }
254}
255
256#[derive(Debug, Default, Clone)]
257pub struct Status {
258 pub item_count: u32,
259 pub matched_count: u32,
260 pub running: bool,
261 pub changed: bool,
262}
263
264#[derive(Debug, thiserror::Error)]
265pub enum WorkerError {
266 #[error("the matcher injector has been shut down")]
267 InjectorShutdown,
268 #[error("{0}")]
269 Custom(&'static str),
270}
271
272pub type WorkerResults<'a, T> = Vec<(Vec<Text<'a>>, &'a T)>;
274
275impl<T: SSS> Worker<T> {
276 pub fn results(
284 &mut self,
285 start: u32,
286 end: u32,
287 width_limits: &[u16],
288 highlight_style: Style,
289 matcher: &mut nucleo::Matcher,
290 ) -> (WorkerResults<'_, T>, Vec<u16>, Status) {
291 let (snapshot, status) = Self::new_snapshot(&mut self.nucleo);
292
293 let mut widths = vec![0u16; self.columns.len()];
294
295 let iter =
296 snapshot.matched_items(start.min(status.matched_count)..end.min(status.matched_count));
297
298 let table = iter
299 .map(|item| {
300 let mut widths = widths.iter_mut();
301
302 let row = self
303 .columns
304 .iter()
305 .enumerate()
306 .zip(width_limits.iter().chain(std::iter::repeat(&u16::MAX)))
307 .map(|((col_idx, column), &width_limit)| {
308 let max_width = widths.next().unwrap();
309 let cell = column.format(item.data);
310
311 if width_limit == 0 {
313 return Text::default();
314 }
315
316 let (cell, width) = if column.filter {
317 render_cell(
318 cell,
319 col_idx,
320 snapshot,
321 &item,
322 matcher,
323 highlight_style,
324 width_limit,
325 &mut self.col_indices_buffer,
326 )
327 } else if width_limit != u16::MAX {
328 let (cell, wrapped) = wrap_text(cell, width_limit - 1);
329
330 let width = if wrapped {
331 width_limit as usize
332 } else {
333 cell.width()
334 };
335 (cell, width)
336 } else {
337 let width = cell.width();
338 (cell, width)
339 };
340
341 if width as u16 > *max_width {
343 *max_width = width as u16;
344 }
345
346 cell
347 });
348
349 (row.collect(), item.data)
350 })
351 .collect();
352
353 for (w, c) in widths.iter_mut().zip(self.columns.iter()) {
355 let name_width = c.name.width() as u16;
356 if *w != 0 {
357 *w = (*w).max(name_width);
358 }
359 }
360
361 (table, widths, status)
362 }
363
364 pub fn exact_column_match(&mut self, column: &str) -> Option<&T> {
365 let (i, col) = self
366 .columns
367 .iter()
368 .enumerate()
369 .find(|(_, c)| column == &*c.name)?;
370
371 let query = self.query.get(column).map(|s| &**s).or_else(|| {
372 self.column_options[i]
373 .contains(ColumnOptions::OrUseDefault)
374 .then(|| self.query.primary_column_query())
375 .flatten()
376 })?;
377
378 let snapshot = self.nucleo.snapshot();
379 snapshot.matched_items(..).find_map(|item| {
380 let content = col.format_text(item.data);
381 if content.as_str() == query {
382 Some(item.data)
383 } else {
384 None
385 }
386 })
387 }
388
389 pub fn format_with<'a>(&'a self, item: &'a T, col: &str) -> Option<Cow<'a, str>> {
390 self.columns
391 .iter()
392 .find(|c| &*c.name == col)
393 .map(|c| c.format_text(item))
394 }
395}
396
397fn render_cell<T: SSS>(
398 cell: Text<'_>,
399 col_idx: usize,
400 snapshot: &nucleo::Snapshot<T>,
401 item: &nucleo::Item<T>,
402 matcher: &mut nucleo::Matcher,
403 highlight_style: Style,
404 width_limit: u16,
405 col_indices_buffer: &mut Vec<u32>,
406) -> (Text<'static>, usize) {
407 let mut cell_width = 0;
408 let mut wrapped = false;
409
410 let indices_buffer = col_indices_buffer;
412 indices_buffer.clear();
413 snapshot.pattern().column_pattern(col_idx).indices(
414 item.matcher_columns[col_idx].slice(..),
415 matcher,
416 indices_buffer,
417 );
418 indices_buffer.sort_unstable();
419 indices_buffer.dedup();
420 let mut indices = indices_buffer.drain(..);
421
422 let mut lines = vec![];
423 let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
424 let mut grapheme_idx = 0u32;
425
426 for line in cell {
427 let mut current_spans = Vec::new();
428 let mut current_span = String::new();
429 let mut current_style = Style::default();
430 let mut current_width = 0;
431
432 for span in line {
433 if width_limit != u16::MAX {}
440 let mut graphemes = span.content.graphemes(true).peekable();
441 while let Some(grapheme) = graphemes.next() {
442 let grapheme_width = UnicodeWidthStr::width(grapheme);
443
444 if width_limit != u16::MAX {
445 if current_width + grapheme_width > (width_limit - 1) as usize && {
446 grapheme_width > 1 || graphemes.peek().is_some()
447 } {
448 current_spans.push(Span::styled(current_span, current_style));
449 current_spans.push(Span::styled(
450 "↵",
451 Style::default().add_modifier(Modifier::DIM),
452 ));
453 lines.push(Line::from(current_spans));
454
455 current_spans = Vec::new();
456 current_span = String::new();
457 current_width = 0;
458 wrapped = true;
459 }
460 }
461
462 let style = if grapheme_idx == next_highlight_idx {
463 next_highlight_idx = indices.next().unwrap_or(u32::MAX);
464 span.style.patch(highlight_style)
465 } else {
466 span.style
467 };
468
469 if style != current_style {
470 if !current_span.is_empty() {
471 current_spans.push(Span::styled(current_span, current_style))
472 }
473 current_span = String::new();
474 current_style = style;
475 }
476 current_span.push_str(grapheme);
477 grapheme_idx += 1;
478 current_width += grapheme_width;
479 }
480 }
481
482 current_spans.push(Span::styled(current_span, current_style));
483 lines.push(Line::from(current_spans));
484 cell_width = cell_width.max(current_width);
485 grapheme_idx += 1; }
487
488 (
489 Text::from(lines),
490 if wrapped {
491 width_limit as usize
492 } else {
493 cell_width
494 },
495 )
496}