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(Debug, Clone)]
 29pub enum SearchEvent {
 30    MatchesInvalidated,
 31    ActiveMatchChanged,
 32}
 33
 34#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
 35pub enum Direction {
 36    Prev,
 37    #[default]
 38    Next,
 39}
 40
 41impl Direction {
 42    pub fn opposite(&self) -> Self {
 43        match self {
 44            Direction::Prev => Direction::Next,
 45            Direction::Next => Direction::Prev,
 46        }
 47    }
 48}
 49
 50#[derive(Clone, Copy, Debug, Default)]
 51pub struct SearchOptions {
 52    pub case: bool,
 53    pub word: bool,
 54    pub regex: bool,
 55    /// Specifies whether the  supports search & replace.
 56    pub replacement: bool,
 57    pub selection: bool,
 58    pub select_all: bool,
 59    pub find_in_results: bool,
 60}
 61
 62// Whether to always select the current selection (even if empty)
 63// or to use the default (restoring the previous search ranges if some,
 64// otherwise using the whole file).
 65#[derive(Clone, Copy, Debug, Default, PartialEq)]
 66pub enum FilteredSearchRange {
 67    Selection,
 68    #[default]
 69    Default,
 70}
 71
 72pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 73    type Match: Any + Sync + Send + Clone;
 74
 75    fn supported_options(&self) -> SearchOptions {
 76        SearchOptions {
 77            case: true,
 78            word: true,
 79            regex: true,
 80            replacement: true,
 81            selection: true,
 82            select_all: true,
 83            find_in_results: false,
 84        }
 85    }
 86
 87    fn search_bar_visibility_changed(
 88        &mut self,
 89        _visible: bool,
 90        _window: &mut Window,
 91        _cx: &mut Context<Self>,
 92    ) {
 93    }
 94
 95    fn has_filtered_search_ranges(&mut self) -> bool {
 96        self.supported_options().selection
 97    }
 98
 99    fn toggle_filtered_search_ranges(
100        &mut self,
101        _enabled: Option<FilteredSearchRange>,
102        _window: &mut Window,
103        _cx: &mut Context<Self>,
104    ) {
105    }
106
107    fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
108        (Vec::new(), SearchToken::default())
109    }
110    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
111    fn update_matches(
112        &mut self,
113        matches: &[Self::Match],
114        active_match_index: Option<usize>,
115        token: SearchToken,
116        window: &mut Window,
117        cx: &mut Context<Self>,
118    );
119    fn query_suggestion(
120        &mut self,
121        ignore_settings: bool,
122        window: &mut Window,
123        cx: &mut Context<Self>,
124    ) -> 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, ignore_settings: bool, 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, ignore_settings: bool, window: &mut Window, cx: &mut App) -> String {
344        self.update(cx, |this, cx| {
345            this.query_suggestion(ignore_settings, window, cx)
346        })
347    }
348    fn activate_match(
349        &self,
350        index: usize,
351        matches: &AnyVec<dyn Send>,
352        token: SearchToken,
353        window: &mut Window,
354        cx: &mut App,
355    ) {
356        let matches = matches.downcast_ref().unwrap();
357        self.update(cx, |this, cx| {
358            this.activate_match(index, matches.as_slice(), token, window, cx)
359        });
360    }
361
362    fn select_matches(
363        &self,
364        matches: &AnyVec<dyn Send>,
365        token: SearchToken,
366        window: &mut Window,
367        cx: &mut App,
368    ) {
369        let matches = matches.downcast_ref().unwrap();
370        self.update(cx, |this, cx| {
371            this.select_matches(matches.as_slice(), token, window, cx)
372        });
373    }
374
375    fn match_index_for_direction(
376        &self,
377        matches: &AnyVec<dyn Send>,
378        current_index: usize,
379        direction: Direction,
380        count: usize,
381        token: SearchToken,
382        window: &mut Window,
383        cx: &mut App,
384    ) -> usize {
385        let matches = matches.downcast_ref().unwrap();
386        self.update(cx, |this, cx| {
387            this.match_index_for_direction(
388                matches.as_slice(),
389                current_index,
390                direction,
391                count,
392                token,
393                window,
394                cx,
395            )
396        })
397    }
398    fn find_matches(
399        &self,
400        query: Arc<SearchQuery>,
401        window: &mut Window,
402        cx: &mut App,
403    ) -> Task<AnyVec<dyn Send>> {
404        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
405        window.spawn(cx, async |_| {
406            let matches = matches.await;
407            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
408            {
409                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
410                for mat in matches {
411                    any_matches.push(mat);
412                }
413            }
414            any_matches
415        })
416    }
417    fn find_matches_with_token(
418        &self,
419        query: Arc<SearchQuery>,
420        window: &mut Window,
421        cx: &mut App,
422    ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
423        let matches_with_token = self.update(cx, |this, cx| {
424            this.find_matches_with_token(query, window, cx)
425        });
426        window.spawn(cx, async |_| {
427            let (matches, token) = matches_with_token.await;
428            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
429            {
430                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
431                for mat in matches {
432                    any_matches.push(mat);
433                }
434            }
435            (any_matches, token)
436        })
437    }
438    fn active_match_index(
439        &self,
440        direction: Direction,
441        matches: &AnyVec<dyn Send>,
442        token: SearchToken,
443        window: &mut Window,
444        cx: &mut App,
445    ) -> Option<usize> {
446        let matches = matches.downcast_ref()?;
447        self.update(cx, |this, cx| {
448            this.active_match_index(direction, matches.as_slice(), token, window, cx)
449        })
450    }
451
452    fn replace(
453        &self,
454        mat: any_vec::element::ElementRef<'_, dyn Send>,
455        query: &SearchQuery,
456        token: SearchToken,
457        window: &mut Window,
458        cx: &mut App,
459    ) {
460        let mat = mat.downcast_ref().unwrap();
461        self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
462    }
463
464    fn replace_all(
465        &self,
466        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
467        query: &SearchQuery,
468        token: SearchToken,
469        window: &mut Window,
470        cx: &mut App,
471    ) {
472        self.update(cx, |this, cx| {
473            this.replace_all(
474                &mut matches.map(|m| m.downcast_ref().unwrap()),
475                query,
476                token,
477                window,
478                cx,
479            );
480        })
481    }
482
483    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
484        self.update(cx, |this, cx| {
485            this.search_bar_visibility_changed(visible, window, cx)
486        });
487    }
488
489    fn toggle_filtered_search_ranges(
490        &mut self,
491        enabled: Option<FilteredSearchRange>,
492        window: &mut Window,
493        cx: &mut App,
494    ) {
495        self.update(cx, |this, cx| {
496            this.toggle_filtered_search_ranges(enabled, window, cx)
497        });
498    }
499    fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
500        self.update(cx, |this, cx| {
501            this.set_search_is_case_sensitive(enabled, cx)
502        });
503    }
504}
505
506impl From<Box<dyn SearchableItemHandle>> for AnyView {
507    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
508        this.to_any_view()
509    }
510}
511
512impl From<&Box<dyn SearchableItemHandle>> for AnyView {
513    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
514        this.to_any_view()
515    }
516}
517
518impl PartialEq for Box<dyn SearchableItemHandle> {
519    fn eq(&self, other: &Self) -> bool {
520        self.item_id() == other.item_id()
521    }
522}
523
524impl Eq for Box<dyn SearchableItemHandle> {}
525
526pub trait WeakSearchableItemHandle: WeakItemHandle {
527    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
528
529    fn into_any(self) -> AnyWeakEntity;
530}
531
532impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
533    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
534        Some(Box::new(self.upgrade()?))
535    }
536
537    fn into_any(self) -> AnyWeakEntity {
538        self.into()
539    }
540}
541
542impl PartialEq for Box<dyn WeakSearchableItemHandle> {
543    fn eq(&self, other: &Self) -> bool {
544        self.id() == other.id()
545    }
546}
547
548impl Eq for Box<dyn WeakSearchableItemHandle> {}
549
550impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
551    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
552        self.id().hash(state)
553    }
554}