matchmaker/nucleo/
variants.rs

1use std::{borrow::Cow, sync::Arc};
2
3use crate::{
4    MMItem, RenderFn, nucleo::Indexed,
5};
6
7use super::{injector::{self}, Text, worker::{Column, Worker}};
8
9// C is not generic because not sure about how C should be used/passed
10impl<T: MMItem> Worker<T> {
11    /// Returns a function which templates a string given an item using the column functions
12    pub fn make_format_fn<const QUOTE: bool>(
13        &self,
14        blank_format: impl Fn(&T) -> &str + Send + Sync + 'static,
15    ) -> RenderFn<T> {
16        let columns = self.columns.clone();
17
18        Box::new(move |item: &T, template: &str| {
19            let mut result = String::with_capacity(template.len());
20            let chars = template.chars().peekable();
21            let mut state = State::Normal;
22            let mut key = String::new();
23
24            enum State {
25                Normal,
26                InKey,
27                Escape,
28            }
29
30            for c in chars {
31                match state {
32                    State::Normal => match c {
33                        '\\' => state = State::Escape,
34                        '{' => state = State::InKey,
35                        _ => result.push(c),
36                    },
37                    State::Escape => {
38                        result.push(c);
39                        state = State::Normal;
40                    }
41                    State::InKey => match c {
42                        '}' => {
43                            let replacement = match key.as_str() {
44                                "" => Cow::Borrowed(blank_format(item)),
45                                _ => columns
46                                .iter()
47                                .find(|col| &*col.name == key.as_str())
48                                .map(|col| col.format_text(item, &()))
49                                .unwrap_or_else(|| Cow::Borrowed("")),
50                            };
51
52                            if QUOTE {
53                                result.push('\'');
54                                result.push_str(&replacement);
55                                result.push('\'');
56                            } else {
57                                result.push_str(&replacement);
58                            }
59                            key.clear();
60                            state = State::Normal;
61                        }
62                        _ => key.push(c),
63                    },
64                }
65            }
66
67            if !key.is_empty() {
68                result.push('{');
69                result.push_str(&key);
70            }
71
72            result
73        })
74    }
75}
76
77pub trait Render {
78    fn as_str(&self) -> &str;
79}
80impl<T: AsRef<str>> Render for T {
81    fn as_str(&self) -> &str {
82        self.as_ref()
83    }
84}
85
86impl<T: Render + MMItem> Worker<Indexed<T>> {
87    /// Create a new worker over items which are displayed in the picker as exactly their as_str representation.
88    pub fn new_single_column() -> Self {
89        Self::new(
90            vec![Column::new("_", |item: &Indexed<T>, _context: &()| {
91                Text::from(item.inner.as_str())
92            })],
93            0,
94            (),
95        )
96    }
97
98    /// A convenience method to initialize data. Note that it is clearly unsound to use this concurrently with other workers, or to subsequently push with an IndexedInjector.
99    pub fn append(&self, items: impl IntoIterator<Item = T>) -> u32 {
100        let mut index = self.nucleo.snapshot().item_count();
101        for inner in items {
102            injector::push_impl(
103                &self.nucleo.injector(),
104                &self.columns,
105                Indexed { index, inner },
106                &(),
107            );
108            index += 1;
109        }
110        index
111    }
112}
113
114pub trait ColumnIndexable {
115    fn index(&self, i: usize) -> &str;
116}
117
118impl<T> Worker<T>
119where
120T: ColumnIndexable + MMItem,
121{
122    /// Create a new worker over indexable items, whose columns as displayed in the picker correspond to indices according to the relative order of the column names given to this function.
123    pub fn new_indexable<I, S>(column_names: I) -> Self
124    where
125    I: IntoIterator<Item = S>,
126    S: Into<Arc<str>>,
127    {
128        let columns = column_names.into_iter().enumerate().map(|(i, name)| {
129            let name = name.into();
130
131            Column::new(name, move |item: &T, _| {
132                let text = item.index(i);
133                Text::from(text)
134            })
135        });
136
137        Self::new(columns, 0, ())
138    }
139}