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}