1use std::{
5 borrow::Cow,
6 sync::{
7 Arc,
8 atomic::{self, AtomicU32},
9 },
10};
11use super::{
12 Style, Stylize,
13 Line, Span, Text,
14};
15use unicode_segmentation::UnicodeSegmentation;
16use unicode_width::UnicodeWidthStr;
17
18use crate::{PickerItem, utils::text::plain_text};
19
20use super::{injector::WorkerInjector, query::PickerQuery};
21
22type ColumnFormatFn<T, C> = Box<dyn for<'a> Fn(&'a T, &'a C) -> Text<'a> + Send + Sync>;
23pub struct Column<T, D = ()> {
24 pub name: Arc<str>,
25 pub(super) format: ColumnFormatFn<T, D>,
26 pub(super) filter: bool,
28 pub(super) hidden: bool,
29}
30
31impl<T, D> Column<T, D> {
32 pub fn new_boxed(name: impl Into<Arc<str>>, format: ColumnFormatFn<T, D>) -> Self {
33 Self {
34 name: name.into(),
35 format,
36 filter: true,
37 hidden: false,
38 }
39 }
40
41 pub fn new<F>(name: impl Into<Arc<str>>, f: F) -> Self
42 where
43 F: for<'a> Fn(&'a T, &'a D) -> Text<'a> + Send + Sync + 'static,
44 {
45 Self {
46 name: name.into(),
47 format: Box::new(f),
48 filter: true,
49 hidden: false,
50 }
51 }
52
53 pub fn with_hidden(mut self) -> Self {
54 self.hidden = true;
55 self
56 }
57
58 pub fn without_filtering(mut self) -> Self {
59 self.filter = false;
60 self
61 }
62
63 fn format<'a>(&self, item: &'a T, context: &'a D) -> Text<'a> {
64 (self.format)(item, context)
65 }
66
67 pub(super) fn format_text<'a>(&self, item: &'a T, context: &'a D) -> Cow<'a, str> {
68 Cow::Owned(plain_text(&(self.format)(item, context)))
69 }
70}
71
72pub struct Worker<T, C = ()>
74where
75T: PickerItem,
76{
77 pub(super) nucleo: nucleo::Nucleo<T>,
79 pub(super) query: PickerQuery,
81 pub(super) col_indices_buffer: Vec<u32>,
84 pub(crate) columns: Arc<[Column<T, C>]>,
85 pub(super) context: Arc<C>,
86
87 pub(super) version: Arc<AtomicU32>,
89}
90
91impl<T, C> Worker<T, C>
92where
93T: PickerItem,
94{
95 pub fn new(
96 columns: impl IntoIterator<Item = Column<T, C>>,
97 default_column: usize,
98 context: C,
99 ) -> Self {
100 let columns: Arc<[_]> = columns.into_iter().collect();
101 let matcher_columns = columns.iter().filter(|col| col.filter).count() as u32;
102
103 let inner = nucleo::Nucleo::new(
104 nucleo::Config::DEFAULT,
105 Arc::new(|| {}),
106 None,
107 matcher_columns,
108 );
109
110 Self {
111 nucleo: inner,
112 col_indices_buffer: Vec::with_capacity(128),
113 query: PickerQuery::new(columns.iter().map(|col| &col.name).cloned(), default_column),
114 columns,
115 context: Arc::new(context),
116 version: Arc::new(AtomicU32::new(0)),
117 }
118 }
119
120 pub fn injector(&self) -> WorkerInjector<T, C> {
121 WorkerInjector {
122 inner: self.nucleo.injector(),
123 columns: self.columns.clone(),
124 context: self.context.clone(),
125 version: self.version.load(atomic::Ordering::Relaxed),
126 picker_version: self.version.clone(),
127 }
128 }
129
130 pub fn results(
131 &mut self,
132 start: u32,
133 end: u32,
134 matcher: &mut nucleo::Matcher,
135 ) -> (Vec<(Vec<Text<'_>>, &T, u16)>, Vec<u16>, Status) {
136 let (snapshot, status) = Self::new_snapshot(&mut self.nucleo);
137 let highlight_style = Style::new().green(); let mut widths = vec![0u16; self.columns.len()];
140
141 let iter =
142 snapshot.matched_items(start.min(status.matched_count)..end.min(status.matched_count));
143
144 let table = iter
145 .map(|item| {
146 let mut widths = widths.iter_mut();
147 let mut col_idx = 0;
148 let mut height = 0;
149
150 let row = self.columns.iter().map(|column| {
151 if column.hidden {
152 return Text::from("");
153 }
154
155 let max_width = widths.next().unwrap();
156 let mut cell = column.format(item.data, &self.context);
157
158 let width = if column.filter {
159 let mut cell_width = 0;
160
161 let indices_buffer = &mut self.col_indices_buffer;
163 indices_buffer.clear();
164 snapshot.pattern().column_pattern(col_idx).indices(
165 item.matcher_columns[col_idx].slice(..),
166 matcher,
167 indices_buffer,
168 );
169 indices_buffer.sort_unstable();
170 indices_buffer.dedup();
171 let mut indices = indices_buffer.drain(..);
172
173 let mut lines = vec![];
174 let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
175 let mut grapheme_idx = 0u32;
176
177 for line in cell {
178 let mut span_list = Vec::new();
179 let mut current_span = String::new();
180 let mut current_style = Style::default();
181 let mut width = 0;
182
183 for span in line {
184 for grapheme in span.content.graphemes(true) {
190 let style = if grapheme_idx == next_highlight_idx {
191 next_highlight_idx = indices.next().unwrap_or(u32::MAX);
192 span.style.patch(highlight_style)
193 } else {
194 span.style
195 };
196 if style != current_style {
197 if !current_span.is_empty() {
198 span_list
199 .push(Span::styled(current_span, current_style))
200 }
201 current_span = String::new();
202 current_style = style;
203 }
204 current_span.push_str(grapheme);
205 grapheme_idx += 1;
206 }
207 width += span.width();
208 }
209
210 span_list.push(Span::styled(current_span, current_style));
211 lines.push(Line::from(span_list));
212 cell_width = cell_width.max(width);
213 grapheme_idx += 1; }
215
216 col_idx += 1;
217 cell = Text::from(lines);
218 cell_width
219 } else {
220 cell.lines.iter().map(|l| l.width()).max().unwrap()
221 };
222
223 if width as u16 > *max_width {
225 *max_width = width as u16;
226 }
227 if cell.height() as u16 > height {
228 height = cell.height() as u16;
229 }
230
231 Text::from(cell)
232 });
233
234 (row.collect(), item.data, height)
235 })
236 .collect();
237
238 for (w, c) in widths.iter_mut().zip(self.columns.iter()) {
239 let name_width = c.name.width() as u16;
240 if *w != 0 {
241 *w = (*w).max(name_width);
242 }
243 }
244
245 (table, widths, status)
246 }
247
248 pub fn find(&mut self, line: &str) {
249 let old_query = self.query.parse(line);
250 if self.query == old_query {
251 return;
252 }
253 for (i, column) in self
254 .columns
255 .iter()
256 .filter(|column| column.filter)
257 .enumerate()
258 {
259 let pattern = self
260 .query
261 .get(&column.name)
262 .map(|f| &**f)
263 .unwrap_or_default();
264 let old_pattern = old_query
265 .get(&column.name)
266 .map(|f| &**f)
267 .unwrap_or_default();
268 if pattern == old_pattern {
270 continue;
271 }
272 let is_append = pattern.starts_with(old_pattern);
273 self.nucleo.pattern.reparse(
274 i,
275 pattern,
276 nucleo::pattern::CaseMatching::Smart,
277 nucleo::pattern::Normalization::Smart,
278 is_append,
279 );
280 }
281 }
282
283 pub fn shutdown(&mut self) {
285 todo!()
286 }
287
288 pub fn restart(&mut self, clear_snapshot: bool) {
289 self.nucleo.restart(clear_snapshot);
290 }
291
292 pub fn get_nth(&self, n: u32) -> Option<&T> {
294 self.nucleo
295 .snapshot()
296 .get_matched_item(n)
297 .map(|item| item.data)
298 }
299
300 pub fn new_snapshot(nucleo: &mut nucleo::Nucleo<T>) -> (&nucleo::Snapshot<T>, Status) {
301 let nucleo::Status { changed, running } = nucleo.tick(10);
302 let snapshot = nucleo.snapshot();
303 (
304 snapshot,
305 Status {
306 item_count: snapshot.item_count(),
307 matched_count: snapshot.matched_item_count(),
308 running,
309 changed,
310 },
311 )
312 }
313
314 pub fn raw_results(&self) -> impl ExactSizeIterator<Item = &T> + DoubleEndedIterator + '_ {
315 let snapshot = self.nucleo.snapshot();
316 snapshot.matched_items(..).map(|item| item.data)
317 }
318
319 pub fn counts(&self) -> (u32, u32) {
321 let snapshot = self.nucleo.snapshot();
322 (snapshot.matched_item_count(), snapshot.item_count())
323 }
324}
325
326#[derive(Debug, Default, Clone)]
327pub struct Status {
328 pub item_count: u32,
329 pub matched_count: u32,
330 pub running: bool,
331 pub changed: bool,
332}
333
334
335#[derive(Debug, thiserror::Error)]
336pub enum WorkerError {
337 #[error("the matcher injector has been shut down")]
338 InjectorShutdown,
339}