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        window: &mut Window,
100        cx: &mut Context<Self>,
101    );
102    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
103    fn activate_match(
104        &mut self,
105        index: usize,
106        matches: &[Self::Match],
107        collapse: bool,
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(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
184    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
185    fn activate_match(
186        &self,
187        index: usize,
188        matches: &AnyVec<dyn Send>,
189        collapse: bool,
190        window: &mut Window,
191        cx: &mut App,
192    );
193    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
194    fn replace(
195        &self,
196        _: any_vec::element::ElementRef<'_, dyn Send>,
197        _: &SearchQuery,
198        _window: &mut Window,
199        _: &mut App,
200    );
201    fn replace_all(
202        &self,
203        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
204        query: &SearchQuery,
205        window: &mut Window,
206        cx: &mut App,
207    );
208    fn match_index_for_direction(
209        &self,
210        matches: &AnyVec<dyn Send>,
211        current_index: usize,
212        direction: Direction,
213        count: usize,
214        window: &mut Window,
215        cx: &mut App,
216    ) -> usize;
217    fn find_matches(
218        &self,
219        query: Arc<SearchQuery>,
220        window: &mut Window,
221        cx: &mut App,
222    ) -> Task<AnyVec<dyn Send>>;
223    fn active_match_index(
224        &self,
225        direction: Direction,
226        matches: &AnyVec<dyn Send>,
227        window: &mut Window,
228        cx: &mut App,
229    ) -> Option<usize>;
230    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
231
232    fn toggle_filtered_search_ranges(
233        &mut self,
234        enabled: Option<FilteredSearchRange>,
235        window: &mut Window,
236        cx: &mut App,
237    );
238
239    fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
240}
241
242impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
243    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
244        Box::new(self.downgrade())
245    }
246
247    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
248        Box::new(self.clone())
249    }
250
251    fn supported_options(&self, cx: &App) -> SearchOptions {
252        self.read(cx).supported_options()
253    }
254
255    fn subscribe_to_search_events(
256        &self,
257        window: &mut Window,
258        cx: &mut App,
259        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
260    ) -> Subscription {
261        window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
262            handler(event, window, cx)
263        })
264    }
265
266    fn clear_matches(&self, window: &mut Window, cx: &mut App) {
267        self.update(cx, |this, cx| this.clear_matches(window, cx));
268    }
269    fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
270        let matches = matches.downcast_ref().unwrap();
271        self.update(cx, |this, cx| {
272            this.update_matches(matches.as_slice(), window, cx)
273        });
274    }
275    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
276        self.update(cx, |this, cx| this.query_suggestion(window, cx))
277    }
278    fn activate_match(
279        &self,
280        index: usize,
281        matches: &AnyVec<dyn Send>,
282        collapse: bool,
283        window: &mut Window,
284        cx: &mut App,
285    ) {
286        let matches = matches.downcast_ref().unwrap();
287        self.update(cx, |this, cx| {
288            this.activate_match(index, matches.as_slice(), collapse, window, cx)
289        });
290    }
291
292    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
293        let matches = matches.downcast_ref().unwrap();
294        self.update(cx, |this, cx| {
295            this.select_matches(matches.as_slice(), window, cx)
296        });
297    }
298
299    fn match_index_for_direction(
300        &self,
301        matches: &AnyVec<dyn Send>,
302        current_index: usize,
303        direction: Direction,
304        count: usize,
305        window: &mut Window,
306        cx: &mut App,
307    ) -> usize {
308        let matches = matches.downcast_ref().unwrap();
309        self.update(cx, |this, cx| {
310            this.match_index_for_direction(
311                matches.as_slice(),
312                current_index,
313                direction,
314                count,
315                window,
316                cx,
317            )
318        })
319    }
320    fn find_matches(
321        &self,
322        query: Arc<SearchQuery>,
323        window: &mut Window,
324        cx: &mut App,
325    ) -> Task<AnyVec<dyn Send>> {
326        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
327        window.spawn(cx, async |_| {
328            let matches = matches.await;
329            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
330            {
331                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
332                for mat in matches {
333                    any_matches.push(mat);
334                }
335            }
336            any_matches
337        })
338    }
339    fn active_match_index(
340        &self,
341        direction: Direction,
342        matches: &AnyVec<dyn Send>,
343        window: &mut Window,
344        cx: &mut App,
345    ) -> Option<usize> {
346        let matches = matches.downcast_ref()?;
347        self.update(cx, |this, cx| {
348            this.active_match_index(direction, matches.as_slice(), window, cx)
349        })
350    }
351
352    fn replace(
353        &self,
354        mat: any_vec::element::ElementRef<'_, dyn Send>,
355        query: &SearchQuery,
356        window: &mut Window,
357        cx: &mut App,
358    ) {
359        let mat = mat.downcast_ref().unwrap();
360        self.update(cx, |this, cx| this.replace(mat, query, window, cx))
361    }
362
363    fn replace_all(
364        &self,
365        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
366        query: &SearchQuery,
367        window: &mut Window,
368        cx: &mut App,
369    ) {
370        self.update(cx, |this, cx| {
371            this.replace_all(
372                &mut matches.map(|m| m.downcast_ref().unwrap()),
373                query,
374                window,
375                cx,
376            );
377        })
378    }
379
380    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
381        self.update(cx, |this, cx| {
382            this.search_bar_visibility_changed(visible, window, cx)
383        });
384    }
385
386    fn toggle_filtered_search_ranges(
387        &mut self,
388        enabled: Option<FilteredSearchRange>,
389        window: &mut Window,
390        cx: &mut App,
391    ) {
392        self.update(cx, |this, cx| {
393            this.toggle_filtered_search_ranges(enabled, window, cx)
394        });
395    }
396    fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
397        self.update(cx, |this, cx| {
398            this.set_search_is_case_sensitive(enabled, cx)
399        });
400    }
401}
402
403impl From<Box<dyn SearchableItemHandle>> for AnyView {
404    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
405        this.to_any()
406    }
407}
408
409impl From<&Box<dyn SearchableItemHandle>> for AnyView {
410    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
411        this.to_any()
412    }
413}
414
415impl PartialEq for Box<dyn SearchableItemHandle> {
416    fn eq(&self, other: &Self) -> bool {
417        self.item_id() == other.item_id()
418    }
419}
420
421impl Eq for Box<dyn SearchableItemHandle> {}
422
423pub trait WeakSearchableItemHandle: WeakItemHandle {
424    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
425
426    fn into_any(self) -> AnyWeakEntity;
427}
428
429impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
430    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
431        Some(Box::new(self.upgrade()?))
432    }
433
434    fn into_any(self) -> AnyWeakEntity {
435        self.into()
436    }
437}
438
439impl PartialEq for Box<dyn WeakSearchableItemHandle> {
440    fn eq(&self, other: &Self) -> bool {
441        self.id() == other.id()
442    }
443}
444
445impl Eq for Box<dyn WeakSearchableItemHandle> {}
446
447impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
448    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
449        self.id().hash(state)
450    }
451}