searchable.rs

  1use std::{any::Any, sync::Arc};
  2
  3use any_vec::AnyVec;
  4use gpui::{
  5    AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
  6    WeakView, WindowContext,
  7};
  8use project::search::SearchQuery;
  9
 10use crate::{
 11    item::{Item, WeakItemHandle},
 12    ItemHandle,
 13};
 14
 15#[derive(Clone, Debug)]
 16pub enum SearchEvent {
 17    MatchesInvalidated,
 18    ActiveMatchChanged,
 19}
 20
 21#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
 22pub enum Direction {
 23    Prev,
 24    #[default]
 25    Next,
 26}
 27
 28impl Direction {
 29    pub fn opposite(&self) -> Self {
 30        match self {
 31            Direction::Prev => Direction::Next,
 32            Direction::Next => Direction::Prev,
 33        }
 34    }
 35}
 36
 37#[derive(Clone, Copy, Debug, Default)]
 38pub struct SearchOptions {
 39    pub case: bool,
 40    pub word: bool,
 41    pub regex: bool,
 42    /// Specifies whether the  supports search & replace.
 43    pub replacement: bool,
 44    pub selection: bool,
 45}
 46
 47pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 48    type Match: Any + Sync + Send + Clone;
 49
 50    fn supported_options() -> SearchOptions {
 51        SearchOptions {
 52            case: true,
 53            word: true,
 54            regex: true,
 55            replacement: true,
 56            selection: true,
 57        }
 58    }
 59
 60    fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {}
 61
 62    fn has_filtered_search_ranges(&mut self) -> bool {
 63        Self::supported_options().selection
 64    }
 65
 66    fn toggle_filtered_search_ranges(&mut self, _enabled: bool, _cx: &mut ViewContext<Self>) {}
 67
 68    fn get_matches(&self, _: &mut WindowContext) -> Vec<Self::Match> {
 69        Vec::new()
 70    }
 71    fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
 72    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 73    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
 74    fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 75    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 76    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
 77    fn replace_all(
 78        &mut self,
 79        matches: &mut dyn Iterator<Item = &Self::Match>,
 80        query: &SearchQuery,
 81        cx: &mut ViewContext<Self>,
 82    ) {
 83        for item in matches {
 84            self.replace(item, query, cx);
 85        }
 86    }
 87    fn match_index_for_direction(
 88        &mut self,
 89        matches: &[Self::Match],
 90        current_index: usize,
 91        direction: Direction,
 92        count: usize,
 93        _: &mut ViewContext<Self>,
 94    ) -> usize {
 95        match direction {
 96            Direction::Prev => {
 97                let count = count % matches.len();
 98                if current_index >= count {
 99                    current_index - count
100                } else {
101                    matches.len() - (count - current_index)
102                }
103            }
104            Direction::Next => (current_index + count) % matches.len(),
105        }
106    }
107    fn find_matches(
108        &mut self,
109        query: Arc<SearchQuery>,
110        cx: &mut ViewContext<Self>,
111    ) -> Task<Vec<Self::Match>>;
112    fn active_match_index(
113        &mut self,
114        matches: &[Self::Match],
115        cx: &mut ViewContext<Self>,
116    ) -> Option<usize>;
117}
118
119pub trait SearchableItemHandle: ItemHandle {
120    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
121    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
122    fn supported_options(&self) -> SearchOptions;
123    fn subscribe_to_search_events(
124        &self,
125        cx: &mut WindowContext,
126        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
127    ) -> Subscription;
128    fn clear_matches(&self, cx: &mut WindowContext);
129    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
130    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
131    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
132    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
133    fn replace(
134        &self,
135        _: any_vec::element::ElementRef<'_, dyn Send>,
136        _: &SearchQuery,
137        _: &mut WindowContext,
138    );
139    fn replace_all(
140        &self,
141        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
142        query: &SearchQuery,
143        cx: &mut WindowContext,
144    );
145    fn match_index_for_direction(
146        &self,
147        matches: &AnyVec<dyn Send>,
148        current_index: usize,
149        direction: Direction,
150        count: usize,
151        cx: &mut WindowContext,
152    ) -> usize;
153    fn find_matches(
154        &self,
155        query: Arc<SearchQuery>,
156        cx: &mut WindowContext,
157    ) -> Task<AnyVec<dyn Send>>;
158    fn active_match_index(
159        &self,
160        matches: &AnyVec<dyn Send>,
161        cx: &mut WindowContext,
162    ) -> Option<usize>;
163    fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
164
165    fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext);
166}
167
168impl<T: SearchableItem> SearchableItemHandle for View<T> {
169    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
170        Box::new(self.downgrade())
171    }
172
173    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
174        Box::new(self.clone())
175    }
176
177    fn supported_options(&self) -> SearchOptions {
178        T::supported_options()
179    }
180
181    fn subscribe_to_search_events(
182        &self,
183        cx: &mut WindowContext,
184        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
185    ) -> Subscription {
186        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
187    }
188
189    fn clear_matches(&self, cx: &mut WindowContext) {
190        self.update(cx, |this, cx| this.clear_matches(cx));
191    }
192    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
193        let matches = matches.downcast_ref().unwrap();
194        self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
195    }
196    fn query_suggestion(&self, cx: &mut WindowContext) -> String {
197        self.update(cx, |this, cx| this.query_suggestion(cx))
198    }
199    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
200        let matches = matches.downcast_ref().unwrap();
201        self.update(cx, |this, cx| {
202            this.activate_match(index, matches.as_slice(), cx)
203        });
204    }
205
206    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
207        let matches = matches.downcast_ref().unwrap();
208        self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
209    }
210
211    fn match_index_for_direction(
212        &self,
213        matches: &AnyVec<dyn Send>,
214        current_index: usize,
215        direction: Direction,
216        count: usize,
217        cx: &mut WindowContext,
218    ) -> usize {
219        let matches = matches.downcast_ref().unwrap();
220        self.update(cx, |this, cx| {
221            this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
222        })
223    }
224    fn find_matches(
225        &self,
226        query: Arc<SearchQuery>,
227        cx: &mut WindowContext,
228    ) -> Task<AnyVec<dyn Send>> {
229        let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
230        cx.spawn(|_| async {
231            let matches = matches.await;
232            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
233            {
234                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
235                for mat in matches {
236                    any_matches.push(mat);
237                }
238            }
239            any_matches
240        })
241    }
242    fn active_match_index(
243        &self,
244        matches: &AnyVec<dyn Send>,
245        cx: &mut WindowContext,
246    ) -> Option<usize> {
247        let matches = matches.downcast_ref()?;
248        self.update(cx, |this, cx| {
249            this.active_match_index(matches.as_slice(), cx)
250        })
251    }
252
253    fn replace(
254        &self,
255        mat: any_vec::element::ElementRef<'_, dyn Send>,
256        query: &SearchQuery,
257        cx: &mut WindowContext,
258    ) {
259        let mat = mat.downcast_ref().unwrap();
260        self.update(cx, |this, cx| this.replace(mat, query, cx))
261    }
262
263    fn replace_all(
264        &self,
265        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
266        query: &SearchQuery,
267        cx: &mut WindowContext,
268    ) {
269        self.update(cx, |this, cx| {
270            this.replace_all(&mut matches.map(|m| m.downcast_ref().unwrap()), query, cx);
271        })
272    }
273
274    fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
275        self.update(cx, |this, cx| {
276            this.search_bar_visibility_changed(visible, cx)
277        });
278    }
279
280    fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext) {
281        self.update(cx, |this, cx| {
282            this.toggle_filtered_search_ranges(enabled, cx)
283        });
284    }
285}
286
287impl From<Box<dyn SearchableItemHandle>> for AnyView {
288    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
289        this.to_any().clone()
290    }
291}
292
293impl From<&Box<dyn SearchableItemHandle>> for AnyView {
294    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
295        this.to_any().clone()
296    }
297}
298
299impl PartialEq for Box<dyn SearchableItemHandle> {
300    fn eq(&self, other: &Self) -> bool {
301        self.item_id() == other.item_id()
302    }
303}
304
305impl Eq for Box<dyn SearchableItemHandle> {}
306
307pub trait WeakSearchableItemHandle: WeakItemHandle {
308    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
309
310    fn into_any(self) -> AnyWeakView;
311}
312
313impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
314    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
315        Some(Box::new(self.upgrade()?))
316    }
317
318    fn into_any(self) -> AnyWeakView {
319        self.into()
320    }
321}
322
323impl PartialEq for Box<dyn WeakSearchableItemHandle> {
324    fn eq(&self, other: &Self) -> bool {
325        self.id() == other.id()
326    }
327}
328
329impl Eq for Box<dyn WeakSearchableItemHandle> {}
330
331impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
332    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
333        self.id().hash(state)
334    }
335}