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