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 item supports search & replace.
 43    pub replacement: bool,
 44}
 45
 46pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 47    type Match: Any + Sync + Send + Clone;
 48
 49    fn supported_options() -> SearchOptions {
 50        SearchOptions {
 51            case: true,
 52            word: true,
 53            regex: true,
 54            replacement: true,
 55        }
 56    }
 57
 58    fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {}
 59
 60    fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
 61    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 62    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
 63    fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 64    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 65    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
 66    fn match_index_for_direction(
 67        &mut self,
 68        matches: &[Self::Match],
 69        current_index: usize,
 70        direction: Direction,
 71        count: usize,
 72        _: &mut ViewContext<Self>,
 73    ) -> usize {
 74        match direction {
 75            Direction::Prev => {
 76                let count = count % matches.len();
 77                if current_index >= count {
 78                    current_index - count
 79                } else {
 80                    matches.len() - (count - current_index)
 81                }
 82            }
 83            Direction::Next => (current_index + count) % matches.len(),
 84        }
 85    }
 86    fn find_matches(
 87        &mut self,
 88        query: Arc<SearchQuery>,
 89        cx: &mut ViewContext<Self>,
 90    ) -> Task<Vec<Self::Match>>;
 91    fn active_match_index(
 92        &mut self,
 93        matches: &[Self::Match],
 94        cx: &mut ViewContext<Self>,
 95    ) -> Option<usize>;
 96}
 97
 98pub trait SearchableItemHandle: ItemHandle {
 99    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
100    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
101    fn supported_options(&self) -> SearchOptions;
102    fn subscribe_to_search_events(
103        &self,
104        cx: &mut WindowContext,
105        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
106    ) -> Subscription;
107    fn clear_matches(&self, cx: &mut WindowContext);
108    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
109    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
110    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
111    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
112    fn replace(
113        &self,
114        _: any_vec::element::ElementRef<'_, dyn Send>,
115        _: &SearchQuery,
116        _: &mut WindowContext,
117    );
118    fn match_index_for_direction(
119        &self,
120        matches: &AnyVec<dyn Send>,
121        current_index: usize,
122        direction: Direction,
123        count: usize,
124        cx: &mut WindowContext,
125    ) -> usize;
126    fn find_matches(
127        &self,
128        query: Arc<SearchQuery>,
129        cx: &mut WindowContext,
130    ) -> Task<AnyVec<dyn Send>>;
131    fn active_match_index(
132        &self,
133        matches: &AnyVec<dyn Send>,
134        cx: &mut WindowContext,
135    ) -> Option<usize>;
136    fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
137}
138
139impl<T: SearchableItem> SearchableItemHandle for View<T> {
140    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
141        Box::new(self.downgrade())
142    }
143
144    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
145        Box::new(self.clone())
146    }
147
148    fn supported_options(&self) -> SearchOptions {
149        T::supported_options()
150    }
151
152    fn subscribe_to_search_events(
153        &self,
154        cx: &mut WindowContext,
155        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
156    ) -> Subscription {
157        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
158    }
159
160    fn clear_matches(&self, cx: &mut WindowContext) {
161        self.update(cx, |this, cx| this.clear_matches(cx));
162    }
163    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
164        let matches = matches.downcast_ref().unwrap();
165        self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
166    }
167    fn query_suggestion(&self, cx: &mut WindowContext) -> String {
168        self.update(cx, |this, cx| this.query_suggestion(cx))
169    }
170    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
171        let matches = matches.downcast_ref().unwrap();
172        self.update(cx, |this, cx| {
173            this.activate_match(index, matches.as_slice(), cx)
174        });
175    }
176
177    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
178        let matches = matches.downcast_ref().unwrap();
179        self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
180    }
181
182    fn match_index_for_direction(
183        &self,
184        matches: &AnyVec<dyn Send>,
185        current_index: usize,
186        direction: Direction,
187        count: usize,
188        cx: &mut WindowContext,
189    ) -> usize {
190        let matches = matches.downcast_ref().unwrap();
191        self.update(cx, |this, cx| {
192            this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
193        })
194    }
195    fn find_matches(
196        &self,
197        query: Arc<SearchQuery>,
198        cx: &mut WindowContext,
199    ) -> Task<AnyVec<dyn Send>> {
200        let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
201        cx.spawn(|_| async {
202            let matches = matches.await;
203            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
204            {
205                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
206                for mat in matches {
207                    any_matches.push(mat);
208                }
209            }
210            any_matches
211        })
212    }
213    fn active_match_index(
214        &self,
215        matches: &AnyVec<dyn Send>,
216        cx: &mut WindowContext,
217    ) -> Option<usize> {
218        let matches = matches.downcast_ref()?;
219        self.update(cx, |this, cx| {
220            this.active_match_index(matches.as_slice(), cx)
221        })
222    }
223
224    fn replace(
225        &self,
226        mat: any_vec::element::ElementRef<'_, dyn Send>,
227        query: &SearchQuery,
228        cx: &mut WindowContext,
229    ) {
230        let mat = mat.downcast_ref().unwrap();
231        self.update(cx, |this, cx| this.replace(mat, query, cx))
232    }
233
234    fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
235        self.update(cx, |this, cx| {
236            this.search_bar_visibility_changed(visible, cx)
237        });
238    }
239}
240
241impl From<Box<dyn SearchableItemHandle>> for AnyView {
242    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
243        this.to_any().clone()
244    }
245}
246
247impl From<&Box<dyn SearchableItemHandle>> for AnyView {
248    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
249        this.to_any().clone()
250    }
251}
252
253impl PartialEq for Box<dyn SearchableItemHandle> {
254    fn eq(&self, other: &Self) -> bool {
255        self.item_id() == other.item_id()
256    }
257}
258
259impl Eq for Box<dyn SearchableItemHandle> {}
260
261pub trait WeakSearchableItemHandle: WeakItemHandle {
262    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
263
264    fn into_any(self) -> AnyWeakView;
265}
266
267impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
268    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
269        Some(Box::new(self.upgrade()?))
270    }
271
272    fn into_any(self) -> AnyWeakView {
273        self.into()
274    }
275}
276
277impl PartialEq for Box<dyn WeakSearchableItemHandle> {
278    fn eq(&self, other: &Self) -> bool {
279        self.id() == other.id()
280    }
281}
282
283impl Eq for Box<dyn WeakSearchableItemHandle> {}
284
285impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
286    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
287        self.id().hash(state)
288    }
289}