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