searchable.rs

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