searchable.rs

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