matchmaker/nucleo/
variants.rs

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