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