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