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    fn get_text(&self, i: usize) -> Text<'_> {
125        Text::from(self.get_str(i))
126    }
127}
128
129impl<T> Worker<T>
130where
131    T: ColumnIndexable + SSS,
132{
133    /// 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.
134    /// # Example
135    /// ```rust
136    /// #[derive(Clone)]
137    /// pub struct RunAction {
138    ///     name: String,
139    ///     alias: String,
140    ///     desc: String
141    /// };
142    ///
143    /// use matchmaker::{Matchmaker, Selector};
144    /// use matchmaker::nucleo::{Indexed, Worker, ColumnIndexable};
145    ///
146    /// impl ColumnIndexable for RunAction {
147    ///     fn get_str(&self, i: usize) -> std::borrow::Cow<'_, str> {
148    ///         if i == 0 {
149    ///             &self.name
150    ///         } else if i == 1 {
151    ///             &self.alias
152    ///         } else {
153    ///             &self.desc
154    ///         }.into()
155    ///     }
156    /// }
157    ///
158    /// pub fn make_mm(
159    ///     items: impl Iterator<Item = RunAction>,
160    /// ) -> Matchmaker<Indexed<RunAction>, RunAction> {
161    ///     let worker = Worker::new_indexable(["name", "alias", "desc"]);
162    ///     worker.append(items);
163    ///     let selector = Selector::new(Indexed::identifier);
164    ///     Matchmaker::new(worker, selector)
165    /// }
166    /// ```
167    pub fn new_indexable<I, S>(column_names: I) -> Self
168    where
169        I: IntoIterator<Item = S>,
170        S: Into<Arc<str>>,
171    {
172        let columns = column_names.into_iter().enumerate().map(|(i, name)| {
173            let name = name.into();
174
175            Column::new(name, move |item: &T| item.get_text(i))
176        });
177
178        Self::new(columns, 0)
179    }
180}