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 const 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        window: &mut Window,
108        cx: &mut Context<Self>,
109    );
110    fn select_matches(
111        &mut self,
112        matches: &[Self::Match],
113        window: &mut Window,
114        cx: &mut Context<Self>,
115    );
116    fn replace(
117        &mut self,
118        _: &Self::Match,
119        _: &SearchQuery,
120        _window: &mut Window,
121        _: &mut Context<Self>,
122    );
123    fn replace_all(
124        &mut self,
125        matches: &mut dyn Iterator<Item = &Self::Match>,
126        query: &SearchQuery,
127        window: &mut Window,
128        cx: &mut Context<Self>,
129    ) {
130        for item in matches {
131            self.replace(item, query, window, cx);
132        }
133    }
134    fn match_index_for_direction(
135        &mut self,
136        matches: &[Self::Match],
137        current_index: usize,
138        direction: Direction,
139        count: usize,
140        _window: &mut Window,
141        _: &mut Context<Self>,
142    ) -> usize {
143        match direction {
144            Direction::Prev => {
145                let count = count % matches.len();
146                if current_index >= count {
147                    current_index - count
148                } else {
149                    matches.len() - (count - current_index)
150                }
151            }
152            Direction::Next => (current_index + count) % matches.len(),
153        }
154    }
155    fn find_matches(
156        &mut self,
157        query: Arc<SearchQuery>,
158        window: &mut Window,
159        cx: &mut Context<Self>,
160    ) -> Task<Vec<Self::Match>>;
161    fn active_match_index(
162        &mut self,
163        direction: Direction,
164        matches: &[Self::Match],
165        window: &mut Window,
166        cx: &mut Context<Self>,
167    ) -> Option<usize>;
168}
169
170pub trait SearchableItemHandle: ItemHandle {
171    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
172    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
173    fn supported_options(&self, cx: &App) -> SearchOptions;
174    fn subscribe_to_search_events(
175        &self,
176        window: &mut Window,
177        cx: &mut App,
178        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
179    ) -> Subscription;
180    fn clear_matches(&self, window: &mut Window, cx: &mut App);
181    fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
182    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
183    fn activate_match(
184        &self,
185        index: usize,
186        matches: &AnyVec<dyn Send>,
187        window: &mut Window,
188        cx: &mut App,
189    );
190    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
191    fn replace(
192        &self,
193        _: any_vec::element::ElementRef<'_, dyn Send>,
194        _: &SearchQuery,
195        _window: &mut Window,
196        _: &mut App,
197    );
198    fn replace_all(
199        &self,
200        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
201        query: &SearchQuery,
202        window: &mut Window,
203        cx: &mut App,
204    );
205    fn match_index_for_direction(
206        &self,
207        matches: &AnyVec<dyn Send>,
208        current_index: usize,
209        direction: Direction,
210        count: usize,
211        window: &mut Window,
212        cx: &mut App,
213    ) -> usize;
214    fn find_matches(
215        &self,
216        query: Arc<SearchQuery>,
217        window: &mut Window,
218        cx: &mut App,
219    ) -> Task<AnyVec<dyn Send>>;
220    fn active_match_index(
221        &self,
222        direction: Direction,
223        matches: &AnyVec<dyn Send>,
224        window: &mut Window,
225        cx: &mut App,
226    ) -> Option<usize>;
227    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
228
229    fn toggle_filtered_search_ranges(
230        &mut self,
231        enabled: Option<FilteredSearchRange>,
232        window: &mut Window,
233        cx: &mut App,
234    );
235}
236
237impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
238    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
239        Box::new(self.downgrade())
240    }
241
242    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
243        Box::new(self.clone())
244    }
245
246    fn supported_options(&self, cx: &App) -> SearchOptions {
247        self.read(cx).supported_options()
248    }
249
250    fn subscribe_to_search_events(
251        &self,
252        window: &mut Window,
253        cx: &mut App,
254        handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
255    ) -> Subscription {
256        window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
257            handler(event, window, cx)
258        })
259    }
260
261    fn clear_matches(&self, window: &mut Window, cx: &mut App) {
262        self.update(cx, |this, cx| this.clear_matches(window, cx));
263    }
264    fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
265        let matches = matches.downcast_ref().unwrap();
266        self.update(cx, |this, cx| {
267            this.update_matches(matches.as_slice(), window, cx)
268        });
269    }
270    fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
271        self.update(cx, |this, cx| this.query_suggestion(window, cx))
272    }
273    fn activate_match(
274        &self,
275        index: usize,
276        matches: &AnyVec<dyn Send>,
277        window: &mut Window,
278        cx: &mut App,
279    ) {
280        let matches = matches.downcast_ref().unwrap();
281        self.update(cx, |this, cx| {
282            this.activate_match(index, matches.as_slice(), window, cx)
283        });
284    }
285
286    fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
287        let matches = matches.downcast_ref().unwrap();
288        self.update(cx, |this, cx| {
289            this.select_matches(matches.as_slice(), window, cx)
290        });
291    }
292
293    fn match_index_for_direction(
294        &self,
295        matches: &AnyVec<dyn Send>,
296        current_index: usize,
297        direction: Direction,
298        count: usize,
299        window: &mut Window,
300        cx: &mut App,
301    ) -> usize {
302        let matches = matches.downcast_ref().unwrap();
303        self.update(cx, |this, cx| {
304            this.match_index_for_direction(
305                matches.as_slice(),
306                current_index,
307                direction,
308                count,
309                window,
310                cx,
311            )
312        })
313    }
314    fn find_matches(
315        &self,
316        query: Arc<SearchQuery>,
317        window: &mut Window,
318        cx: &mut App,
319    ) -> Task<AnyVec<dyn Send>> {
320        let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
321        window.spawn(cx, async |_| {
322            let matches = matches.await;
323            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
324            {
325                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
326                for mat in matches {
327                    any_matches.push(mat);
328                }
329            }
330            any_matches
331        })
332    }
333    fn active_match_index(
334        &self,
335        direction: Direction,
336        matches: &AnyVec<dyn Send>,
337        window: &mut Window,
338        cx: &mut App,
339    ) -> Option<usize> {
340        let matches = matches.downcast_ref()?;
341        self.update(cx, |this, cx| {
342            this.active_match_index(direction, matches.as_slice(), window, cx)
343        })
344    }
345
346    fn replace(
347        &self,
348        mat: any_vec::element::ElementRef<'_, dyn Send>,
349        query: &SearchQuery,
350        window: &mut Window,
351        cx: &mut App,
352    ) {
353        let mat = mat.downcast_ref().unwrap();
354        self.update(cx, |this, cx| this.replace(mat, query, window, cx))
355    }
356
357    fn replace_all(
358        &self,
359        matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
360        query: &SearchQuery,
361        window: &mut Window,
362        cx: &mut App,
363    ) {
364        self.update(cx, |this, cx| {
365            this.replace_all(
366                &mut matches.map(|m| m.downcast_ref().unwrap()),
367                query,
368                window,
369                cx,
370            );
371        })
372    }
373
374    fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
375        self.update(cx, |this, cx| {
376            this.search_bar_visibility_changed(visible, window, cx)
377        });
378    }
379
380    fn toggle_filtered_search_ranges(
381        &mut self,
382        enabled: Option<FilteredSearchRange>,
383        window: &mut Window,
384        cx: &mut App,
385    ) {
386        self.update(cx, |this, cx| {
387            this.toggle_filtered_search_ranges(enabled, window, cx)
388        });
389    }
390}
391
392impl From<Box<dyn SearchableItemHandle>> for AnyView {
393    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
394        this.to_any()
395    }
396}
397
398impl From<&Box<dyn SearchableItemHandle>> for AnyView {
399    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
400        this.to_any()
401    }
402}
403
404impl PartialEq for Box<dyn SearchableItemHandle> {
405    fn eq(&self, other: &Self) -> bool {
406        self.item_id() == other.item_id()
407    }
408}
409
410impl Eq for Box<dyn SearchableItemHandle> {}
411
412pub trait WeakSearchableItemHandle: WeakItemHandle {
413    fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
414
415    fn into_any(self) -> AnyWeakEntity;
416}
417
418impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
419    fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
420        Some(Box::new(self.upgrade()?))
421    }
422
423    fn into_any(self) -> AnyWeakEntity {
424        self.into()
425    }
426}
427
428impl PartialEq for Box<dyn WeakSearchableItemHandle> {
429    fn eq(&self, other: &Self) -> bool {
430        self.id() == other.id()
431    }
432}
433
434impl Eq for Box<dyn WeakSearchableItemHandle> {}
435
436impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
437    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
438        self.id().hash(state)
439    }
440}