Skip to main content

matchmaker/nucleo/
variants.rs

1use std::{borrow::Cow, sync::Arc};
2
3use crate::{RenderFn, SSS, nucleo::Indexed, utils::text::plain_text};
4
5use super::{
6    Text,
7    injector::{self},
8    worker::{Column, Worker},
9};
10
11impl<T: SSS> Worker<T> {
12    /// Returns a function which templates a string given an item using the column functions
13    pub fn default_format_fn<const QUOTE: bool>(
14        &self,
15        blank_format: impl Fn(&T) -> Cow<'_, str> + SSS,
16    ) -> RenderFn<T> {
17        let columns = self.columns.clone();
18
19        Box::new(move |item: &T, template: &str| {
20            let mut result = String::with_capacity(template.len());
21            let chars = template.chars().peekable();
22            let mut state = State::Normal;
23            let mut key = String::new();
24
25            enum State {
26                Normal,
27                InKey,
28                Escape,
29            }
30
31            for c in chars {
32                match state {
33                    State::Normal => match c {
34                        '\\' => state = State::Escape,
35                        '{' => state = State::InKey,
36                        _ => result.push(c),
37                    },
38                    State::Escape => {
39                        result.push(c);
40                        state = State::Normal;
41                    }
42                    State::InKey => match c {
43                        '}' => {
44                            let replacement = match key.as_str() {
45                                "" => blank_format(item),
46                                _ => columns
47                                    .iter()
48                                    .find(|col| &*col.name == key.as_str())
49                                    .map(|col| col.format_text(item))
50                                    .unwrap_or_else(|| Cow::Borrowed("")),
51                            };
52
53                            if QUOTE {
54                                result.push('\'');
55                                result.push_str(&replacement);
56                                result.push('\'');
57                            } else {
58                                result.push_str(&replacement);
59                            }
60                            key.clear();
61                            state = State::Normal;
62                        }
63                        _ => key.push(c),
64                    },
65                }
66            }
67
68            if !key.is_empty() {
69                result.push('{');
70                result.push_str(&key);
71            }
72
73            result
74        })
75    }
76}
77
78impl<T: SSS> Worker<Indexed<T>> {
79    /// A convenience method to initialize data. Items are indexed starting from the current nucleo item count.
80    /// # Notes
81    /// -  Not concurrent.
82    /// - Subsequent use of IndexedInjector should start from the returned count.
83    pub fn append(&self, items: impl IntoIterator<Item = T>) -> u32 {
84        let mut index = self.nucleo.snapshot().item_count();
85        for inner in items {
86            injector::push_impl(
87                &self.nucleo.injector(),
88                &self.columns,
89                Indexed { index, inner },
90            );
91            index += 1;
92        }
93        index
94    }
95}
96
97/// You must either impl as_str or as_text
98pub trait Render {
99    fn as_str(&self) -> std::borrow::Cow<'_, str> {
100        plain_text(&self.as_text()).into()
101    }
102    fn as_text(&self) -> Text<'_> {
103        Text::from(self.as_str())
104    }
105}
106impl<T: AsRef<str>> Render for T {
107    fn as_str(&self) -> std::borrow::Cow<'_, str> {
108        self.as_ref().into()
109    }
110}
111
112impl<T: Render + SSS> Worker<T> {
113    /// Create a new worker over items which are displayed in the picker as exactly their as_str representation.
114    pub fn new_single_column() -> Self {
115        Self::new([Column::new("_", |item: &T| item.as_text())], 0)
116    }
117}
118
119/// You must either impl as_str or as_text
120pub trait ColumnIndexable {
121    fn get_str(&self, i: usize) -> std::borrow::Cow<'_, str> {
122        plain_text(&self.get_text(i)).into()
123    }
124
125    fn get_text(&self, i: usize) -> Text<'_> {
126        Text::from(self.get_str(i))
127    }
128}
129
130impl<T> Worker<T>
131where
132    T: ColumnIndexable + SSS,
133{
134    /// Create a new worker over indexable items, whose columns correspond to indices according to the relative order of the column names given to this function.
135    /// # Example
136    /// ```rust
137    /// #[derive(Clone)]
138    /// pub struct RunAction {
139    ///     name: String,
140    ///     alias: String,
141    ///     desc: String
142    /// };
143    ///
144    /// use matchmaker::{Matchmaker, Selector};
145    /// use matchmaker::nucleo::{Indexed, Worker, ColumnIndexable};
146    ///
147    /// impl ColumnIndexable for RunAction {
148    ///     fn get_str(&self, i: usize) -> std::borrow::Cow<'_, str> {
149    ///         if i == 0 {
150    ///             &self.name
151    ///         } else if i == 1 {
152    ///             &self.alias
153    ///         } else {
154    ///             &self.desc
155    ///         }.into()
156    ///     }
157    /// }
158    ///
159    /// pub fn make_mm(
160    ///     items: impl Iterator<Item = RunAction>,
161    /// ) -> Matchmaker<Indexed<RunAction>, RunAction> {
162    ///     let worker = Worker::new_indexable(["name", "alias", "desc"]);
163    ///     worker.append(items);
164    ///     let selector = Selector::new(Indexed::identifier);
165    ///     Matchmaker::new(worker, selector)
166    /// }
167    /// ```
168    pub fn new_indexable<I, S>(column_names: I) -> Self
169    where
170        I: IntoIterator<Item = S>,
171        S: Into<Arc<str>>,
172    {
173        let columns = column_names.into_iter().enumerate().map(|(i, name)| {
174            let name = name.into();
175
176            Column::new(name, move |item: &T| item.get_text(i))
177        });
178
179        Self::new(columns, 0)
180    }
181}