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