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