searchable.rs

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