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(Copy, Clone, Debug, Default, PartialEq, Eq)]
 16pub struct SearchToken(u64);
 17
 18impl SearchToken {
 19    pub fn new(value: u64) -> Self {
 20        Self(value)
 21    }
 22
 23    pub fn value(&self) -> u64 {
 24        self.0
 25    }
 26}
 27
 28#[derive(Clone, Debug)]
 29pub enum CollapseDirection {
 30    Collapsed,
 31    Expanded,
 32}
 33
 34#[derive(Debug, Clone)]
 35pub enum SearchEvent {
 36    MatchesInvalidated,
 37    ActiveMatchChanged,
 38    ResultsCollapsedChanged(CollapseDirection),
 39}
 40
 41#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
 42pub enum Direction {
 43    Prev,
 44    #[default]
 45    Next,
 46}
 47
 48impl Direction {
 49    pub fn opposite(&self) -> Self {
 50        match self {
 51            Direction::Prev => Direction::Next,
 52            Direction::Next => Direction::Prev,
 53        }
 54    }
 55}
 56
 57#[derive(Clone, Copy, Debug, Default)]
 58pub struct SearchOptions {
 59    pub case: bool,
 60    pub word: bool,
 61    pub regex: bool,
 62    /// Specifies whether the  supports search & replace.
 63    pub replacement: bool,
 64    pub selection: bool,
 65    pub find_in_results: bool,
 66}
 67
 68// Whether to always select the current selection (even if empty)
 69// or to use the default (restoring the previous search ranges if some,
 70// otherwise using the whole file).
 71#[derive(Clone, Copy, Debug, Default, PartialEq)]
 72pub enum FilteredSearchRange {
 73    Selection,
 74    #[default]
 75    Default,
 76}
 77
 78pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 79    type Match: Any + Sync + Send + Clone;
 80
 81    fn supported_options(&self) -> SearchOptions {
 82        SearchOptions {
 83            case: true,
 84            word: true,
 85            regex: true,
 86            replacement: true,
 87            selection: true,
 88            find_in_results: false,
 89        }
 90    }
 91
 92    fn search_bar_visibility_changed(
 93        &mut self,
 94        _visible: bool,
 95        _window: &mut Window,
 96        _cx: &mut Context<Self>,
 97    ) {
 98    }
 99
100    fn has_filtered_search_ranges(&mut self) -> bool {
101        self.supported_options().selection
102    }
103
104    fn toggle_filtered_search_ranges(
105        &mut self,
106        _enabled: Option<FilteredSearchRange>,
107        _window: &mut Window,
108        _cx: &mut Context<Self>,
109    ) {
110    }
111
112    fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
113        (Vec::new(), SearchToken::default())
114    }
115    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
116    fn update_matches(
117        &mut self,
118        matches: &[Self::Match],
119        active_match_index: Option<usize>,
120        token: SearchToken,
121        window: &mut Window,
122        cx: &mut Context<Self>,
123    );
124    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
125    fn activate_match(
126        &mut self,
127        index: usize,
128        matches: &[Self::Match],
129        token: SearchToken,
130        window: &mut Window,
131        cx: &mut Context<Self>,
132    );
133    fn select_matches(
134        &mut self,
135        matches: &[Self::Match],
136        token: SearchToken,
137        window: &mut Window,
138        cx: &mut Context<Self>,
139    );
140    fn replace(
141        &mut self,
142        _: &Self::Match,
143        _: &SearchQuery,
144        _token: SearchToken,
145        _window: &mut Window,
146        _: &mut Context<Self>,
147    );
148    fn replace_all(
149        &mut self,
150        matches: &mut dyn Iterator<Item = &Self::Match>,
151        query: &SearchQuery,
152        token: SearchToken,
153        window: &mut Window,
154        cx: &mut Context<Self>,
155    ) {
156        for item in matches {
157            self.replace(item, query, token, window, cx);
158        }
159    }
160    fn match_index_for_direction(
161        &mut self,
162        matches: &[Self::Match],
163        current_index: usize,
164        direction: Direction,
165        count: usize,
166        _token: SearchToken,
167        _window: &mut Window,
168        _: &mut Context<Self>,
169    ) -> usize {
170        match direction {
171            Direction::Prev => {
172                let count = count % matches.len();
173                if current_index >= count {
174                    current_index - count
175                } else {
176                    matches.len() - (count - current_index)
177                }
178            }
179            Direction::Next => (current_index + count) % matches.len(),
180        }
181    }
182    fn find_matches(
183        &mut self,
184        query: Arc<SearchQuery>,
185        window: &mut Window,
186        cx: &mut Context<Self>,
187    ) -> Task<Vec<Self::Match>>;
188
189    fn find_matches_with_token(
190        &mut self,
191        query: Arc<SearchQuery>,
192        window: &mut Window,
193        cx: &mut Context<Self>,
194    ) -> Task<(Vec<Self::Match>, SearchToken)> {
195        let matches = self.find_matches(query, window, cx);
196        cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
197    }
198
199    fn active_match_index(
200        &mut self,
201        direction: Direction,
202        matches: &[Self::Match],
203        token: SearchToken,
204        window: &mut Window,
205        cx: &mut Context<Self>,
206    ) -> Option<usize>;
207    fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
208}
209
210pub trait SearchableItemHandle: ItemHandle {
211    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
212    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
213    fn supported_options(&self, cx: &App) -> SearchOptions;
214    fn subscribe_to_search_events(
215        &self,
216        window: &mut Window,
217        cx: &mut App,
218        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
219    ) -> Subscription;
220    fn clear_matches(&self, window: &mut Window, cx: &mut App);
221    fn update_matches(
222        &self,
223        matches: &AnyVec<dyn Send>,
224        active_match_index: Option<usize>,
225        token: SearchToken,
226        window: &mut Window,
227        cx: &mut App,
228    );
229    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
230    fn activate_match(
231        &self,
232        index: usize,
233        matches: &AnyVec<dyn Send>,
234        token: SearchToken,
235        window: &mut Window,
236        cx: &mut App,
237    );
238    fn select_matches(
239        &self,
240        matches: &AnyVec<dyn Send>,
241        token: SearchToken,
242        window: &mut Window,
243        cx: &mut App,
244    );
245    fn replace(
246        &self,
247        _: any_vec::element::ElementRef<'_, dyn Send>,
248        _: &SearchQuery,
249        token: SearchToken,
250        _window: &mut Window,
251        _: &mut App,
252    );
253    fn replace_all(
254        &self,
255        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
256        query: &SearchQuery,
257        token: SearchToken,
258        window: &mut Window,
259        cx: &mut App,
260    );
261    fn match_index_for_direction(
262        &self,
263        matches: &AnyVec<dyn Send>,
264        current_index: usize,
265        direction: Direction,
266        count: usize,
267        token: SearchToken,
268        window: &mut Window,
269        cx: &mut App,
270    ) -> usize;
271    fn find_matches(
272        &self,
273        query: Arc<SearchQuery>,
274        window: &mut Window,
275        cx: &mut App,
276    ) -> Task<AnyVec<dyn Send>>;
277    fn find_matches_with_token(
278        &self,
279        query: Arc<SearchQuery>,
280        window: &mut Window,
281        cx: &mut App,
282    ) -> Task<(AnyVec<dyn Send>, SearchToken)>;
283    fn active_match_index(
284        &self,
285        direction: Direction,
286        matches: &AnyVec<dyn Send>,
287        token: SearchToken,
288        window: &mut Window,
289        cx: &mut App,
290    ) -> Option<usize>;
291    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
292
293    fn toggle_filtered_search_ranges(
294        &mut self,
295        enabled: Option<FilteredSearchRange>,
296        window: &mut Window,
297        cx: &mut App,
298    );
299
300    fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
301}
302
303impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
304    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
305        Box::new(self.downgrade())
306    }
307
308    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
309        Box::new(self.clone())
310    }
311
312    fn supported_options(&self, cx: &App) -> SearchOptions {
313        self.read(cx).supported_options()
314    }
315
316    fn subscribe_to_search_events(
317        &self,
318        window: &mut Window,
319        cx: &mut App,
320        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
321    ) -> Subscription {
322        window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
323            handler(event, window, cx)
324        })
325    }
326
327    fn clear_matches(&self, window: &mut Window, cx: &mut App) {
328        self.update(cx, |this, cx| this.clear_matches(window, cx));
329    }
330    fn update_matches(
331        &self,
332        matches: &AnyVec<dyn Send>,
333        active_match_index: Option<usize>,
334        token: SearchToken,
335        window: &mut Window,
336        cx: &mut App,
337    ) {
338        let matches = matches.downcast_ref().unwrap();
339        self.update(cx, |this, cx| {
340            this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
341        });
342    }
343    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
344        self.update(cx, |this, cx| this.query_suggestion(window, cx))
345    }
346    fn activate_match(
347        &self,
348        index: usize,
349        matches: &AnyVec<dyn Send>,
350        token: SearchToken,
351        window: &mut Window,
352        cx: &mut App,
353    ) {
354        let matches = matches.downcast_ref().unwrap();
355        self.update(cx, |this, cx| {
356            this.activate_match(index, matches.as_slice(), token, window, cx)
357        });
358    }
359
360    fn select_matches(
361        &self,
362        matches: &AnyVec<dyn Send>,
363        token: SearchToken,
364        window: &mut Window,
365        cx: &mut App,
366    ) {
367        let matches = matches.downcast_ref().unwrap();
368        self.update(cx, |this, cx| {
369            this.select_matches(matches.as_slice(), token, window, cx)
370        });
371    }
372
373    fn match_index_for_direction(
374        &self,
375        matches: &AnyVec<dyn Send>,
376        current_index: usize,
377        direction: Direction,
378        count: usize,
379        token: SearchToken,
380        window: &mut Window,
381        cx: &mut App,
382    ) -> usize {
383        let matches = matches.downcast_ref().unwrap();
384        self.update(cx, |this, cx| {
385            this.match_index_for_direction(
386                matches.as_slice(),
387                current_index,
388                direction,
389                count,
390                token,
391                window,
392                cx,
393            )
394        })
395    }
396    fn find_matches(
397        &self,
398        query: Arc<SearchQuery>,
399        window: &mut Window,
400        cx: &mut App,
401    ) -> Task<AnyVec<dyn Send>> {
402        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
403        window.spawn(cx, async |_| {
404            let matches = matches.await;
405            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
406            {
407                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
408                for mat in matches {
409                    any_matches.push(mat);
410                }
411            }
412            any_matches
413        })
414    }
415    fn find_matches_with_token(
416        &self,
417        query: Arc<SearchQuery>,
418        window: &mut Window,
419        cx: &mut App,
420    ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
421        let matches_with_token = self.update(cx, |this, cx| {
422            this.find_matches_with_token(query, window, cx)
423        });
424        window.spawn(cx, async |_| {
425            let (matches, token) = matches_with_token.await;
426            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
427            {
428                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
429                for mat in matches {
430                    any_matches.push(mat);
431                }
432            }
433            (any_matches, token)
434        })
435    }
436    fn active_match_index(
437        &self,
438        direction: Direction,
439        matches: &AnyVec<dyn Send>,
440        token: SearchToken,
441        window: &mut Window,
442        cx: &mut App,
443    ) -> Option<usize> {
444        let matches = matches.downcast_ref()?;
445        self.update(cx, |this, cx| {
446            this.active_match_index(direction, matches.as_slice(), token, window, cx)
447        })
448    }
449
450    fn replace(
451        &self,
452        mat: any_vec::element::ElementRef<'_, dyn Send>,
453        query: &SearchQuery,
454        token: SearchToken,
455        window: &mut Window,
456        cx: &mut App,
457    ) {
458        let mat = mat.downcast_ref().unwrap();
459        self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
460    }
461
462    fn replace_all(
463        &self,
464        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
465        query: &SearchQuery,
466        token: SearchToken,
467        window: &mut Window,
468        cx: &mut App,
469    ) {
470        self.update(cx, |this, cx| {
471            this.replace_all(
472                &mut matches.map(|m| m.downcast_ref().unwrap()),
473                query,
474                token,
475                window,
476                cx,
477            );
478        })
479    }
480
481    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
482        self.update(cx, |this, cx| {
483            this.search_bar_visibility_changed(visible, window, cx)
484        });
485    }
486
487    fn toggle_filtered_search_ranges(
488        &mut self,
489        enabled: Option<FilteredSearchRange>,
490        window: &mut Window,
491        cx: &mut App,
492    ) {
493        self.update(cx, |this, cx| {
494            this.toggle_filtered_search_ranges(enabled, window, cx)
495        });
496    }
497    fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
498        self.update(cx, |this, cx| {
499            this.set_search_is_case_sensitive(enabled, cx)
500        });
501    }
502}
503
504impl From<Box<dyn SearchableItemHandle>> for AnyView {
505    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
506        this.to_any_view()
507    }
508}
509
510impl From<&Box<dyn SearchableItemHandle>> for AnyView {
511    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
512        this.to_any_view()
513    }
514}
515
516impl PartialEq for Box<dyn SearchableItemHandle> {
517    fn eq(&self, other: &Self) -> bool {
518        self.item_id() == other.item_id()
519    }
520}
521
522impl Eq for Box<dyn SearchableItemHandle> {}
523
524pub trait WeakSearchableItemHandle: WeakItemHandle {
525    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
526
527    fn into_any(self) -> AnyWeakEntity;
528}
529
530impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
531    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
532        Some(Box::new(self.upgrade()?))
533    }
534
535    fn into_any(self) -> AnyWeakEntity {
536        self.into()
537    }
538}
539
540impl PartialEq for Box<dyn WeakSearchableItemHandle> {
541    fn eq(&self, other: &Self) -> bool {
542        self.id() == other.id()
543    }
544}
545
546impl Eq for Box<dyn WeakSearchableItemHandle> {}
547
548impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
549    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
550        self.id().hash(state)
551    }
552}