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(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
120    fn activate_match(
121        &mut self,
122        index: usize,
123        matches: &[Self::Match],
124        token: SearchToken,
125        window: &mut Window,
126        cx: &mut Context<Self>,
127    );
128    fn select_matches(
129        &mut self,
130        matches: &[Self::Match],
131        token: SearchToken,
132        window: &mut Window,
133        cx: &mut Context<Self>,
134    );
135    fn replace(
136        &mut self,
137        _: &Self::Match,
138        _: &SearchQuery,
139        _token: SearchToken,
140        _window: &mut Window,
141        _: &mut Context<Self>,
142    );
143    fn replace_all(
144        &mut self,
145        matches: &mut dyn Iterator<Item = &Self::Match>,
146        query: &SearchQuery,
147        token: SearchToken,
148        window: &mut Window,
149        cx: &mut Context<Self>,
150    ) {
151        for item in matches {
152            self.replace(item, query, token, window, cx);
153        }
154    }
155    fn match_index_for_direction(
156        &mut self,
157        matches: &[Self::Match],
158        current_index: usize,
159        direction: Direction,
160        count: usize,
161        _token: SearchToken,
162        _window: &mut Window,
163        _: &mut Context<Self>,
164    ) -> usize {
165        match direction {
166            Direction::Prev => {
167                let count = count % matches.len();
168                if current_index >= count {
169                    current_index - count
170                } else {
171                    matches.len() - (count - current_index)
172                }
173            }
174            Direction::Next => (current_index + count) % matches.len(),
175        }
176    }
177    fn find_matches(
178        &mut self,
179        query: Arc<SearchQuery>,
180        window: &mut Window,
181        cx: &mut Context<Self>,
182    ) -> Task<Vec<Self::Match>>;
183
184    fn find_matches_with_token(
185        &mut self,
186        query: Arc<SearchQuery>,
187        window: &mut Window,
188        cx: &mut Context<Self>,
189    ) -> Task<(Vec<Self::Match>, SearchToken)> {
190        let matches = self.find_matches(query, window, cx);
191        cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
192    }
193
194    fn active_match_index(
195        &mut self,
196        direction: Direction,
197        matches: &[Self::Match],
198        token: SearchToken,
199        window: &mut Window,
200        cx: &mut Context<Self>,
201    ) -> Option<usize>;
202    fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
203}
204
205pub trait SearchableItemHandle: ItemHandle {
206    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
207    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
208    fn supported_options(&self, cx: &App) -> SearchOptions;
209    fn subscribe_to_search_events(
210        &self,
211        window: &mut Window,
212        cx: &mut App,
213        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
214    ) -> Subscription;
215    fn clear_matches(&self, window: &mut Window, cx: &mut App);
216    fn update_matches(
217        &self,
218        matches: &AnyVec<dyn Send>,
219        active_match_index: Option<usize>,
220        token: SearchToken,
221        window: &mut Window,
222        cx: &mut App,
223    );
224    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
225    fn activate_match(
226        &self,
227        index: usize,
228        matches: &AnyVec<dyn Send>,
229        token: SearchToken,
230        window: &mut Window,
231        cx: &mut App,
232    );
233    fn select_matches(
234        &self,
235        matches: &AnyVec<dyn Send>,
236        token: SearchToken,
237        window: &mut Window,
238        cx: &mut App,
239    );
240    fn replace(
241        &self,
242        _: any_vec::element::ElementRef<'_, dyn Send>,
243        _: &SearchQuery,
244        token: SearchToken,
245        _window: &mut Window,
246        _: &mut App,
247    );
248    fn replace_all(
249        &self,
250        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
251        query: &SearchQuery,
252        token: SearchToken,
253        window: &mut Window,
254        cx: &mut App,
255    );
256    fn match_index_for_direction(
257        &self,
258        matches: &AnyVec<dyn Send>,
259        current_index: usize,
260        direction: Direction,
261        count: usize,
262        token: SearchToken,
263        window: &mut Window,
264        cx: &mut App,
265    ) -> usize;
266    fn find_matches(
267        &self,
268        query: Arc<SearchQuery>,
269        window: &mut Window,
270        cx: &mut App,
271    ) -> Task<AnyVec<dyn Send>>;
272    fn find_matches_with_token(
273        &self,
274        query: Arc<SearchQuery>,
275        window: &mut Window,
276        cx: &mut App,
277    ) -> Task<(AnyVec<dyn Send>, SearchToken)>;
278    fn active_match_index(
279        &self,
280        direction: Direction,
281        matches: &AnyVec<dyn Send>,
282        token: SearchToken,
283        window: &mut Window,
284        cx: &mut App,
285    ) -> Option<usize>;
286    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
287
288    fn toggle_filtered_search_ranges(
289        &mut self,
290        enabled: Option<FilteredSearchRange>,
291        window: &mut Window,
292        cx: &mut App,
293    );
294
295    fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
296}
297
298impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
299    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
300        Box::new(self.downgrade())
301    }
302
303    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
304        Box::new(self.clone())
305    }
306
307    fn supported_options(&self, cx: &App) -> SearchOptions {
308        self.read(cx).supported_options()
309    }
310
311    fn subscribe_to_search_events(
312        &self,
313        window: &mut Window,
314        cx: &mut App,
315        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
316    ) -> Subscription {
317        window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
318            handler(event, window, cx)
319        })
320    }
321
322    fn clear_matches(&self, window: &mut Window, cx: &mut App) {
323        self.update(cx, |this, cx| this.clear_matches(window, cx));
324    }
325    fn update_matches(
326        &self,
327        matches: &AnyVec<dyn Send>,
328        active_match_index: Option<usize>,
329        token: SearchToken,
330        window: &mut Window,
331        cx: &mut App,
332    ) {
333        let matches = matches.downcast_ref().unwrap();
334        self.update(cx, |this, cx| {
335            this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
336        });
337    }
338    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
339        self.update(cx, |this, cx| this.query_suggestion(window, cx))
340    }
341    fn activate_match(
342        &self,
343        index: usize,
344        matches: &AnyVec<dyn Send>,
345        token: SearchToken,
346        window: &mut Window,
347        cx: &mut App,
348    ) {
349        let matches = matches.downcast_ref().unwrap();
350        self.update(cx, |this, cx| {
351            this.activate_match(index, matches.as_slice(), token, window, cx)
352        });
353    }
354
355    fn select_matches(
356        &self,
357        matches: &AnyVec<dyn Send>,
358        token: SearchToken,
359        window: &mut Window,
360        cx: &mut App,
361    ) {
362        let matches = matches.downcast_ref().unwrap();
363        self.update(cx, |this, cx| {
364            this.select_matches(matches.as_slice(), token, window, cx)
365        });
366    }
367
368    fn match_index_for_direction(
369        &self,
370        matches: &AnyVec<dyn Send>,
371        current_index: usize,
372        direction: Direction,
373        count: usize,
374        token: SearchToken,
375        window: &mut Window,
376        cx: &mut App,
377    ) -> usize {
378        let matches = matches.downcast_ref().unwrap();
379        self.update(cx, |this, cx| {
380            this.match_index_for_direction(
381                matches.as_slice(),
382                current_index,
383                direction,
384                count,
385                token,
386                window,
387                cx,
388            )
389        })
390    }
391    fn find_matches(
392        &self,
393        query: Arc<SearchQuery>,
394        window: &mut Window,
395        cx: &mut App,
396    ) -> Task<AnyVec<dyn Send>> {
397        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
398        window.spawn(cx, async |_| {
399            let matches = matches.await;
400            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
401            {
402                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
403                for mat in matches {
404                    any_matches.push(mat);
405                }
406            }
407            any_matches
408        })
409    }
410    fn find_matches_with_token(
411        &self,
412        query: Arc<SearchQuery>,
413        window: &mut Window,
414        cx: &mut App,
415    ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
416        let matches_with_token = self.update(cx, |this, cx| {
417            this.find_matches_with_token(query, window, cx)
418        });
419        window.spawn(cx, async |_| {
420            let (matches, token) = matches_with_token.await;
421            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
422            {
423                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
424                for mat in matches {
425                    any_matches.push(mat);
426                }
427            }
428            (any_matches, token)
429        })
430    }
431    fn active_match_index(
432        &self,
433        direction: Direction,
434        matches: &AnyVec<dyn Send>,
435        token: SearchToken,
436        window: &mut Window,
437        cx: &mut App,
438    ) -> Option<usize> {
439        let matches = matches.downcast_ref()?;
440        self.update(cx, |this, cx| {
441            this.active_match_index(direction, matches.as_slice(), token, window, cx)
442        })
443    }
444
445    fn replace(
446        &self,
447        mat: any_vec::element::ElementRef<'_, dyn Send>,
448        query: &SearchQuery,
449        token: SearchToken,
450        window: &mut Window,
451        cx: &mut App,
452    ) {
453        let mat = mat.downcast_ref().unwrap();
454        self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
455    }
456
457    fn replace_all(
458        &self,
459        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
460        query: &SearchQuery,
461        token: SearchToken,
462        window: &mut Window,
463        cx: &mut App,
464    ) {
465        self.update(cx, |this, cx| {
466            this.replace_all(
467                &mut matches.map(|m| m.downcast_ref().unwrap()),
468                query,
469                token,
470                window,
471                cx,
472            );
473        })
474    }
475
476    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
477        self.update(cx, |this, cx| {
478            this.search_bar_visibility_changed(visible, window, cx)
479        });
480    }
481
482    fn toggle_filtered_search_ranges(
483        &mut self,
484        enabled: Option<FilteredSearchRange>,
485        window: &mut Window,
486        cx: &mut App,
487    ) {
488        self.update(cx, |this, cx| {
489            this.toggle_filtered_search_ranges(enabled, window, cx)
490        });
491    }
492    fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
493        self.update(cx, |this, cx| {
494            this.set_search_is_case_sensitive(enabled, cx)
495        });
496    }
497}
498
499impl From<Box<dyn SearchableItemHandle>> for AnyView {
500    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
501        this.to_any_view()
502    }
503}
504
505impl From<&Box<dyn SearchableItemHandle>> for AnyView {
506    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
507        this.to_any_view()
508    }
509}
510
511impl PartialEq for Box<dyn SearchableItemHandle> {
512    fn eq(&self, other: &Self) -> bool {
513        self.item_id() == other.item_id()
514    }
515}
516
517impl Eq for Box<dyn SearchableItemHandle> {}
518
519pub trait WeakSearchableItemHandle: WeakItemHandle {
520    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
521
522    fn into_any(self) -> AnyWeakEntity;
523}
524
525impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
526    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
527        Some(Box::new(self.upgrade()?))
528    }
529
530    fn into_any(self) -> AnyWeakEntity {
531        self.into()
532    }
533}
534
535impl PartialEq for Box<dyn WeakSearchableItemHandle> {
536    fn eq(&self, other: &Self) -> bool {
537        self.id() == other.id()
538    }
539}
540
541impl Eq for Box<dyn WeakSearchableItemHandle> {}
542
543impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
544    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
545        self.id().hash(state)
546    }
547}