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}