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