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