searchable.rs

  1use std::{any::Any, sync::Arc};
  2
  3use any_vec::AnyVec;
  4use gpui::{
  5    AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
  6    WeakView, WindowContext,
  7};
  8use project::search::SearchQuery;
  9
 10use crate::{
 11    item::{Item, WeakItemHandle},
 12    ItemHandle,
 13};
 14
 15#[derive(Clone, Debug)]
 16pub enum SearchEvent {
 17    MatchesInvalidated,
 18    ActiveMatchChanged,
 19}
 20
 21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 22pub enum Direction {
 23    Prev,
 24    Next,
 25}
 26
 27#[derive(Clone, Copy, Debug, Default)]
 28pub struct SearchOptions {
 29    pub case: bool,
 30    pub word: bool,
 31    pub regex: bool,
 32    /// Specifies whether the item supports search & replace.
 33    pub replacement: bool,
 34}
 35
 36pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 37    type Match: Any + Sync + Send + Clone;
 38
 39    fn supported_options() -> SearchOptions {
 40        SearchOptions {
 41            case: true,
 42            word: true,
 43            regex: true,
 44            replacement: true,
 45        }
 46    }
 47
 48    fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
 49    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 50    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
 51    fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 52    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
 53    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
 54    fn match_index_for_direction(
 55        &mut self,
 56        matches: &[Self::Match],
 57        current_index: usize,
 58        direction: Direction,
 59        count: usize,
 60        _: &mut ViewContext<Self>,
 61    ) -> usize {
 62        match direction {
 63            Direction::Prev => {
 64                let count = count % matches.len();
 65                if current_index >= count {
 66                    current_index - count
 67                } else {
 68                    matches.len() - (count - current_index)
 69                }
 70            }
 71            Direction::Next => (current_index + count) % matches.len(),
 72        }
 73    }
 74    fn find_matches(
 75        &mut self,
 76        query: Arc<SearchQuery>,
 77        cx: &mut ViewContext<Self>,
 78    ) -> Task<Vec<Self::Match>>;
 79    fn active_match_index(
 80        &mut self,
 81        matches: &[Self::Match],
 82        cx: &mut ViewContext<Self>,
 83    ) -> Option<usize>;
 84}
 85
 86pub trait SearchableItemHandle: ItemHandle {
 87    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
 88    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
 89    fn supported_options(&self) -> SearchOptions;
 90    fn subscribe_to_search_events(
 91        &self,
 92        cx: &mut WindowContext,
 93        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
 94    ) -> Subscription;
 95    fn clear_matches(&self, cx: &mut WindowContext);
 96    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
 97    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
 98    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
 99    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
100    fn replace(
101        &self,
102        _: any_vec::element::ElementRef<'_, dyn Send>,
103        _: &SearchQuery,
104        _: &mut WindowContext,
105    );
106    fn match_index_for_direction(
107        &self,
108        matches: &AnyVec<dyn Send>,
109        current_index: usize,
110        direction: Direction,
111        count: usize,
112        cx: &mut WindowContext,
113    ) -> usize;
114    fn find_matches(
115        &self,
116        query: Arc<SearchQuery>,
117        cx: &mut WindowContext,
118    ) -> Task<AnyVec<dyn Send>>;
119    fn active_match_index(
120        &self,
121        matches: &AnyVec<dyn Send>,
122        cx: &mut WindowContext,
123    ) -> Option<usize>;
124}
125
126impl<T: SearchableItem> SearchableItemHandle for View<T> {
127    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
128        Box::new(self.downgrade())
129    }
130
131    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
132        Box::new(self.clone())
133    }
134
135    fn supported_options(&self) -> SearchOptions {
136        T::supported_options()
137    }
138
139    fn subscribe_to_search_events(
140        &self,
141        cx: &mut WindowContext,
142        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
143    ) -> Subscription {
144        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
145    }
146
147    fn clear_matches(&self, cx: &mut WindowContext) {
148        self.update(cx, |this, cx| this.clear_matches(cx));
149    }
150    fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
151        let matches = matches.downcast_ref().unwrap();
152        self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
153    }
154    fn query_suggestion(&self, cx: &mut WindowContext) -> String {
155        self.update(cx, |this, cx| this.query_suggestion(cx))
156    }
157    fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
158        let matches = matches.downcast_ref().unwrap();
159        self.update(cx, |this, cx| {
160            this.activate_match(index, matches.as_slice(), cx)
161        });
162    }
163
164    fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
165        let matches = matches.downcast_ref().unwrap();
166        self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
167    }
168
169    fn match_index_for_direction(
170        &self,
171        matches: &AnyVec<dyn Send>,
172        current_index: usize,
173        direction: Direction,
174        count: usize,
175        cx: &mut WindowContext,
176    ) -> usize {
177        let matches = matches.downcast_ref().unwrap();
178        self.update(cx, |this, cx| {
179            this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
180        })
181    }
182    fn find_matches(
183        &self,
184        query: Arc<SearchQuery>,
185        cx: &mut WindowContext,
186    ) -> Task<AnyVec<dyn Send>> {
187        let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
188        cx.spawn(|_| async {
189            let matches = matches.await;
190            let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
191            {
192                let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
193                for mat in matches {
194                    any_matches.push(mat);
195                }
196            }
197            any_matches
198        })
199    }
200    fn active_match_index(
201        &self,
202        matches: &AnyVec<dyn Send>,
203        cx: &mut WindowContext,
204    ) -> Option<usize> {
205        let matches = matches.downcast_ref()?;
206        self.update(cx, |this, cx| {
207            this.active_match_index(matches.as_slice(), cx)
208        })
209    }
210
211    fn replace(
212        &self,
213        mat: any_vec::element::ElementRef<'_, dyn Send>,
214        query: &SearchQuery,
215        cx: &mut WindowContext,
216    ) {
217        let mat = mat.downcast_ref().unwrap();
218        self.update(cx, |this, cx| this.replace(mat, query, cx))
219    }
220}
221
222impl From<Box<dyn SearchableItemHandle>> for AnyView {
223    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
224        this.to_any().clone()
225    }
226}
227
228impl From<&Box<dyn SearchableItemHandle>> for AnyView {
229    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
230        this.to_any().clone()
231    }
232}
233
234impl PartialEq for Box<dyn SearchableItemHandle> {
235    fn eq(&self, other: &Self) -> bool {
236        self.item_id() == other.item_id()
237    }
238}
239
240impl Eq for Box<dyn SearchableItemHandle> {}
241
242pub trait WeakSearchableItemHandle: WeakItemHandle {
243    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
244
245    fn into_any(self) -> AnyWeakView;
246}
247
248impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
249    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
250        Some(Box::new(self.upgrade()?))
251    }
252
253    fn into_any(self) -> AnyWeakView {
254        self.into()
255    }
256}
257
258impl PartialEq for Box<dyn WeakSearchableItemHandle> {
259    fn eq(&self, other: &Self) -> bool {
260        self.id() == other.id()
261    }
262}
263
264impl Eq for Box<dyn WeakSearchableItemHandle> {}
265
266impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
267    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
268        self.id().hash(state)
269    }
270}