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