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