searchable.rs

  1use std::{any::Any, sync::Arc};
  2
  3use any_vec::AnyVec;
  4use gpui::{
  5    AnyView, AnyWeakEntity, App, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
  6    Window,
  7};
  8use project::search::SearchQuery;
  9
 10use crate::{
 11    ItemHandle,
 12    item::{Item, WeakItemHandle},
 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    pub find_in_results: bool,
 46}
 47
 48// Whether to always select the current selection (even if empty)
 49// or to use the default (restoring the previous search ranges if some,
 50// otherwise using the whole file).
 51#[derive(Clone, Copy, Debug, Default, PartialEq)]
 52pub enum FilteredSearchRange {
 53    Selection,
 54    #[default]
 55    Default,
 56}
 57
 58pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 59    type Match: Any + Sync + Send + Clone;
 60
 61    fn supported_options(&self) -> SearchOptions {
 62        SearchOptions {
 63            case: true,
 64            word: true,
 65            regex: true,
 66            replacement: true,
 67            selection: true,
 68            find_in_results: false,
 69        }
 70    }
 71
 72    fn search_bar_visibility_changed(
 73        &mut self,
 74        _visible: bool,
 75        _window: &mut Window,
 76        _cx: &mut Context<Self>,
 77    ) {
 78    }
 79
 80    fn has_filtered_search_ranges(&mut self) -> bool {
 81        self.supported_options().selection
 82    }
 83
 84    fn toggle_filtered_search_ranges(
 85        &mut self,
 86        _enabled: Option<FilteredSearchRange>,
 87        _window: &mut Window,
 88        _cx: &mut Context<Self>,
 89    ) {
 90    }
 91
 92    fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Self::Match> {
 93        Vec::new()
 94    }
 95    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
 96    fn update_matches(
 97        &mut self,
 98        matches: &[Self::Match],
 99        active_match_index: Option<usize>,
100        window: &mut Window,
101        cx: &mut Context<Self>,
102    );
103    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
104    fn activate_match(
105        &mut self,
106        index: usize,
107        matches: &[Self::Match],
108        window: &mut Window,
109        cx: &mut Context<Self>,
110    );
111    fn select_matches(
112        &mut self,
113        matches: &[Self::Match],
114        window: &mut Window,
115        cx: &mut Context<Self>,
116    );
117    fn replace(
118        &mut self,
119        _: &Self::Match,
120        _: &SearchQuery,
121        _window: &mut Window,
122        _: &mut Context<Self>,
123    );
124    fn replace_all(
125        &mut self,
126        matches: &mut dyn Iterator<Item = &Self::Match>,
127        query: &SearchQuery,
128        window: &mut Window,
129        cx: &mut Context<Self>,
130    ) {
131        for item in matches {
132            self.replace(item, query, window, cx);
133        }
134    }
135    fn match_index_for_direction(
136        &mut self,
137        matches: &[Self::Match],
138        current_index: usize,
139        direction: Direction,
140        count: usize,
141        _window: &mut Window,
142        _: &mut Context<Self>,
143    ) -> usize {
144        match direction {
145            Direction::Prev => {
146                let count = count % matches.len();
147                if current_index >= count {
148                    current_index - count
149                } else {
150                    matches.len() - (count - current_index)
151                }
152            }
153            Direction::Next => (current_index + count) % matches.len(),
154        }
155    }
156    fn find_matches(
157        &mut self,
158        query: Arc<SearchQuery>,
159        window: &mut Window,
160        cx: &mut Context<Self>,
161    ) -> Task<Vec<Self::Match>>;
162    fn active_match_index(
163        &mut self,
164        direction: Direction,
165        matches: &[Self::Match],
166        window: &mut Window,
167        cx: &mut Context<Self>,
168    ) -> Option<usize>;
169    fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
170}
171
172pub trait SearchableItemHandle: ItemHandle {
173    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
174    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
175    fn supported_options(&self, cx: &App) -> SearchOptions;
176    fn subscribe_to_search_events(
177        &self,
178        window: &mut Window,
179        cx: &mut App,
180        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
181    ) -> Subscription;
182    fn clear_matches(&self, window: &mut Window, cx: &mut App);
183    fn update_matches(
184        &self,
185        matches: &AnyVec<dyn Send>,
186        active_match_index: Option<usize>,
187        window: &mut Window,
188        cx: &mut App,
189    );
190    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
191    fn activate_match(
192        &self,
193        index: usize,
194        matches: &AnyVec<dyn Send>,
195        window: &mut Window,
196        cx: &mut App,
197    );
198    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
199    fn replace(
200        &self,
201        _: any_vec::element::ElementRef<'_, dyn Send>,
202        _: &SearchQuery,
203        _window: &mut Window,
204        _: &mut App,
205    );
206    fn replace_all(
207        &self,
208        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
209        query: &SearchQuery,
210        window: &mut Window,
211        cx: &mut App,
212    );
213    fn match_index_for_direction(
214        &self,
215        matches: &AnyVec<dyn Send>,
216        current_index: usize,
217        direction: Direction,
218        count: usize,
219        window: &mut Window,
220        cx: &mut App,
221    ) -> usize;
222    fn find_matches(
223        &self,
224        query: Arc<SearchQuery>,
225        window: &mut Window,
226        cx: &mut App,
227    ) -> Task<AnyVec<dyn Send>>;
228    fn active_match_index(
229        &self,
230        direction: Direction,
231        matches: &AnyVec<dyn Send>,
232        window: &mut Window,
233        cx: &mut App,
234    ) -> Option<usize>;
235    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
236
237    fn toggle_filtered_search_ranges(
238        &mut self,
239        enabled: Option<FilteredSearchRange>,
240        window: &mut Window,
241        cx: &mut App,
242    );
243
244    fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
245}
246
247impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
248    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
249        Box::new(self.downgrade())
250    }
251
252    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
253        Box::new(self.clone())
254    }
255
256    fn supported_options(&self, cx: &App) -> SearchOptions {
257        self.read(cx).supported_options()
258    }
259
260    fn subscribe_to_search_events(
261        &self,
262        window: &mut Window,
263        cx: &mut App,
264        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
265    ) -> Subscription {
266        window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
267            handler(event, window, cx)
268        })
269    }
270
271    fn clear_matches(&self, window: &mut Window, cx: &mut App) {
272        self.update(cx, |this, cx| this.clear_matches(window, cx));
273    }
274    fn update_matches(
275        &self,
276        matches: &AnyVec<dyn Send>,
277        active_match_index: Option<usize>,
278        window: &mut Window,
279        cx: &mut App,
280    ) {
281        let matches = matches.downcast_ref().unwrap();
282        self.update(cx, |this, cx| {
283            this.update_matches(matches.as_slice(), active_match_index, window, cx)
284        });
285    }
286    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
287        self.update(cx, |this, cx| this.query_suggestion(window, cx))
288    }
289    fn activate_match(
290        &self,
291        index: usize,
292        matches: &AnyVec<dyn Send>,
293        window: &mut Window,
294        cx: &mut App,
295    ) {
296        let matches = matches.downcast_ref().unwrap();
297        self.update(cx, |this, cx| {
298            this.activate_match(index, matches.as_slice(), window, cx)
299        });
300    }
301
302    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
303        let matches = matches.downcast_ref().unwrap();
304        self.update(cx, |this, cx| {
305            this.select_matches(matches.as_slice(), window, cx)
306        });
307    }
308
309    fn match_index_for_direction(
310        &self,
311        matches: &AnyVec<dyn Send>,
312        current_index: usize,
313        direction: Direction,
314        count: usize,
315        window: &mut Window,
316        cx: &mut App,
317    ) -> usize {
318        let matches = matches.downcast_ref().unwrap();
319        self.update(cx, |this, cx| {
320            this.match_index_for_direction(
321                matches.as_slice(),
322                current_index,
323                direction,
324                count,
325                window,
326                cx,
327            )
328        })
329    }
330    fn find_matches(
331        &self,
332        query: Arc<SearchQuery>,
333        window: &mut Window,
334        cx: &mut App,
335    ) -> Task<AnyVec<dyn Send>> {
336        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
337        window.spawn(cx, async |_| {
338            let matches = matches.await;
339            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
340            {
341                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
342                for mat in matches {
343                    any_matches.push(mat);
344                }
345            }
346            any_matches
347        })
348    }
349    fn active_match_index(
350        &self,
351        direction: Direction,
352        matches: &AnyVec<dyn Send>,
353        window: &mut Window,
354        cx: &mut App,
355    ) -> Option<usize> {
356        let matches = matches.downcast_ref()?;
357        self.update(cx, |this, cx| {
358            this.active_match_index(direction, matches.as_slice(), window, cx)
359        })
360    }
361
362    fn replace(
363        &self,
364        mat: any_vec::element::ElementRef<'_, dyn Send>,
365        query: &SearchQuery,
366        window: &mut Window,
367        cx: &mut App,
368    ) {
369        let mat = mat.downcast_ref().unwrap();
370        self.update(cx, |this, cx| this.replace(mat, query, window, cx))
371    }
372
373    fn replace_all(
374        &self,
375        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
376        query: &SearchQuery,
377        window: &mut Window,
378        cx: &mut App,
379    ) {
380        self.update(cx, |this, cx| {
381            this.replace_all(
382                &mut matches.map(|m| m.downcast_ref().unwrap()),
383                query,
384                window,
385                cx,
386            );
387        })
388    }
389
390    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
391        self.update(cx, |this, cx| {
392            this.search_bar_visibility_changed(visible, window, cx)
393        });
394    }
395
396    fn toggle_filtered_search_ranges(
397        &mut self,
398        enabled: Option<FilteredSearchRange>,
399        window: &mut Window,
400        cx: &mut App,
401    ) {
402        self.update(cx, |this, cx| {
403            this.toggle_filtered_search_ranges(enabled, window, cx)
404        });
405    }
406    fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
407        self.update(cx, |this, cx| {
408            this.set_search_is_case_sensitive(enabled, cx)
409        });
410    }
411}
412
413impl From<Box<dyn SearchableItemHandle>> for AnyView {
414    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
415        this.to_any_view()
416    }
417}
418
419impl From<&Box<dyn SearchableItemHandle>> for AnyView {
420    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
421        this.to_any_view()
422    }
423}
424
425impl PartialEq for Box<dyn SearchableItemHandle> {
426    fn eq(&self, other: &Self) -> bool {
427        self.item_id() == other.item_id()
428    }
429}
430
431impl Eq for Box<dyn SearchableItemHandle> {}
432
433pub trait WeakSearchableItemHandle: WeakItemHandle {
434    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
435
436    fn into_any(self) -> AnyWeakEntity;
437}
438
439impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
440    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
441        Some(Box::new(self.upgrade()?))
442    }
443
444    fn into_any(self) -> AnyWeakEntity {
445        self.into()
446    }
447}
448
449impl PartialEq for Box<dyn WeakSearchableItemHandle> {
450    fn eq(&self, other: &Self) -> bool {
451        self.id() == other.id()
452    }
453}
454
455impl Eq for Box<dyn WeakSearchableItemHandle> {}
456
457impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
458    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
459        self.id().hash(state)
460    }
461}