Skip to main content

rgpui_component/searchable_list/
vec.rs

1use rgpui::{App, SharedString, Task, Window};
2
3use crate::IndexPath;
4
5use super::delegate::{SearchableListDelegate, SearchableListItem};
6
7// MARK: Primitive impls
8
9impl SearchableListItem for String {
10    type Value = Self;
11
12    fn title(&self) -> SharedString {
13        SharedString::from(self.clone())
14    }
15
16    fn value(&self) -> &Self::Value {
17        self
18    }
19}
20
21impl SearchableListItem for SharedString {
22    type Value = Self;
23
24    fn title(&self) -> SharedString {
25        self.clone()
26    }
27
28    fn value(&self) -> &Self::Value {
29        self
30    }
31}
32
33impl SearchableListItem for &'static str {
34    type Value = Self;
35
36    fn title(&self) -> SharedString {
37        SharedString::from(*self)
38    }
39
40    fn value(&self) -> &Self::Value {
41        self
42    }
43}
44
45// MARK: Vec delegate
46
47impl<T: SearchableListItem + 'static> SearchableListDelegate for Vec<T> {
48    type Item = T;
49
50    fn items_count(&self, _: usize) -> usize {
51        self.len()
52    }
53
54    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
55        self.as_slice().get(ix.row)
56    }
57
58    fn position<V>(&self, value: &V) -> Option<IndexPath>
59    where
60        Self::Item: SearchableListItem<Value = V>,
61        V: PartialEq,
62    {
63        self.iter()
64            .position(|v| v.value() == value)
65            .map(|ix| IndexPath::default().row(ix))
66    }
67}
68
69// MARK: SearchableVec
70
71/// A vector of items that supports incremental filtering.
72///
73/// On each `perform_search` call the `matched_items` view is rebuilt by filtering
74/// the full `items` list.  Use this as a delegate when all data is already in memory.
75#[derive(Debug, Clone)]
76pub struct SearchableVec<T> {
77    items: Vec<T>,
78    matched_items: Vec<T>,
79}
80
81impl<T: Clone> SearchableVec<T> {
82    /// Create a new `SearchableVec` from an initial list of items.
83    pub fn new(items: impl Into<Vec<T>>) -> Self {
84        let items = items.into();
85
86        Self {
87            items: items.clone(),
88            matched_items: items,
89        }
90    }
91
92    /// Append an item to both the master list and the current filtered view.
93    pub fn push(&mut self, item: T) {
94        self.items.push(item.clone());
95        self.matched_items.push(item);
96    }
97}
98
99impl<T: SearchableListItem> From<Vec<T>> for SearchableVec<T> {
100    fn from(items: Vec<T>) -> Self {
101        Self {
102            items: items.clone(),
103            matched_items: items,
104        }
105    }
106}
107
108impl<I: SearchableListItem + 'static> SearchableListDelegate for SearchableVec<I> {
109    type Item = I;
110
111    fn items_count(&self, _: usize) -> usize {
112        self.matched_items.len()
113    }
114
115    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
116        self.matched_items.get(ix.row)
117    }
118
119    fn position<V>(&self, value: &V) -> Option<IndexPath>
120    where
121        Self::Item: SearchableListItem<Value = V>,
122        V: PartialEq,
123    {
124        self.matched_items
125            .iter()
126            .position(|v| v.value() == value)
127            .map(|ix| IndexPath::default().row(ix))
128    }
129
130    fn perform_search(&mut self, query: &str, _: &mut Window, _: &mut App) -> Task<()> {
131        self.matched_items = self
132            .items
133            .iter()
134            .filter(|item| item.matches(query))
135            .cloned()
136            .collect();
137
138        Task::ready(())
139    }
140}
141
142// MARK: SearchableGroup
143
144/// A named group of items used for sectioned lists.
145#[derive(Debug, Clone)]
146pub struct SearchableGroup<I: SearchableListItem> {
147    pub title: SharedString,
148    pub items: Vec<I>,
149}
150
151impl<I: SearchableListItem> SearchableGroup<I> {
152    /// Create an empty group with the given section title.
153    pub fn new(title: impl Into<SharedString>) -> Self {
154        Self {
155            title: title.into(),
156            items: vec![],
157        }
158    }
159
160    /// Append a single item to this group.
161    pub fn item(mut self, item: I) -> Self {
162        self.items.push(item);
163        self
164    }
165
166    /// Append multiple items to this group.
167    pub fn items(mut self, items: impl IntoIterator<Item = I>) -> Self {
168        self.items.extend(items);
169        self
170    }
171
172    pub(super) fn matches(&self, query: &str) -> bool {
173        self.title.to_lowercase().contains(&query.to_lowercase())
174            || self.items.iter().any(|item| item.matches(query))
175    }
176}
177
178impl<I: SearchableListItem + 'static> SearchableListDelegate for SearchableVec<SearchableGroup<I>> {
179    type Item = I;
180
181    fn sections_count(&self, _: &App) -> usize {
182        self.matched_items.len()
183    }
184
185    fn items_count(&self, section: usize) -> usize {
186        self.matched_items
187            .get(section)
188            .map_or(0, |group| group.items.len())
189    }
190
191    fn section(&self, section: usize) -> Option<rgpui::AnyElement> {
192        use rgpui::IntoElement as _;
193
194        Some(
195            self.matched_items
196                .get(section)?
197                .title
198                .clone()
199                .into_any_element(),
200        )
201    }
202
203    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
204        let section = self.matched_items.get(ix.section)?;
205
206        section.items.get(ix.row)
207    }
208
209    fn position<V>(&self, value: &V) -> Option<IndexPath>
210    where
211        Self::Item: SearchableListItem<Value = V>,
212        V: PartialEq,
213    {
214        for (ix, group) in self.matched_items.iter().enumerate() {
215            for (row_ix, item) in group.items.iter().enumerate() {
216                if item.value() == value {
217                    return Some(IndexPath::default().section(ix).row(row_ix));
218                }
219            }
220        }
221
222        None
223    }
224
225    fn perform_search(&mut self, query: &str, _: &mut Window, _: &mut App) -> Task<()> {
226        self.matched_items = self
227            .items
228            .iter()
229            .filter(|item| item.matches(query))
230            .cloned()
231            .map(|mut item| {
232                item.items.retain(|item| item.matches(query));
233                item
234            })
235            .collect();
236
237        Task::ready(())
238    }
239}