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