searchable.rs

  1use std::{any::Any, sync::Arc};
  2
  3use gpui::{
  4    AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
  5    WeakView, WindowContext,
  6};
  7use project::search::SearchQuery;
  8
  9use crate::{
 10    item::{Item, WeakItemHandle},
 11    ItemHandle,
 12};
 13
 14#[derive(Clone, Debug)]
 15pub enum SearchEvent {
 16    MatchesInvalidated,
 17    ActiveMatchChanged,
 18}
 19
 20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 21pub enum Direction {
 22    Prev,
 23    Next,
 24}
 25
 26#[derive(Clone, Copy, Debug, Default)]
 27pub struct SearchOptions {
 28    pub case: bool,
 29    pub word: bool,
 30    pub regex: bool,
 31    /// Specifies whether the item supports search & replace.
 32    pub replacement: bool,
 33}
 34
 35pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
 36    type Match: Any + Sync + Send + Clone;
 37
 38    fn supported_options() -> SearchOptions {
 39        SearchOptions {
 40            case: true,
 41            word: true,
 42            regex: true,
 43            replacement: true,
 44        }
 45    }
 46
 47    fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
 48    fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
 49    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
 50    fn activate_match(
 51        &mut self,
 52        index: usize,
 53        matches: Vec<Self::Match>,
 54        cx: &mut ViewContext<Self>,
 55    );
 56    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
 57    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
 58    fn match_index_for_direction(
 59        &mut self,
 60        matches: &Vec<Self::Match>,
 61        current_index: usize,
 62        direction: Direction,
 63        count: usize,
 64        _: &mut ViewContext<Self>,
 65    ) -> usize {
 66        match direction {
 67            Direction::Prev => {
 68                let count = count % matches.len();
 69                if current_index >= count {
 70                    current_index - count
 71                } else {
 72                    matches.len() - (count - current_index)
 73                }
 74            }
 75            Direction::Next => (current_index + count) % matches.len(),
 76        }
 77    }
 78    fn find_matches(
 79        &mut self,
 80        query: Arc<SearchQuery>,
 81        cx: &mut ViewContext<Self>,
 82    ) -> Task<Vec<Self::Match>>;
 83    fn active_match_index(
 84        &mut self,
 85        matches: Vec<Self::Match>,
 86        cx: &mut ViewContext<Self>,
 87    ) -> Option<usize>;
 88}
 89
 90pub trait SearchableItemHandle: ItemHandle {
 91    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
 92    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
 93    fn supported_options(&self) -> SearchOptions;
 94    fn subscribe_to_search_events(
 95        &self,
 96        cx: &mut WindowContext,
 97        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
 98    ) -> Subscription;
 99    fn clear_matches(&self, cx: &mut WindowContext);
100    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
101    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
102    fn activate_match(
103        &self,
104        index: usize,
105        matches: &Vec<Box<dyn Any + Send>>,
106        cx: &mut WindowContext,
107    );
108    fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
109    fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
110    fn match_index_for_direction(
111        &self,
112        matches: &Vec<Box<dyn Any + Send>>,
113        current_index: usize,
114        direction: Direction,
115        count: usize,
116        cx: &mut WindowContext,
117    ) -> usize;
118    fn find_matches(
119        &self,
120        query: Arc<SearchQuery>,
121        cx: &mut WindowContext,
122    ) -> Task<Vec<Box<dyn Any + Send>>>;
123    fn active_match_index(
124        &self,
125        matches: &Vec<Box<dyn Any + Send>>,
126        cx: &mut WindowContext,
127    ) -> Option<usize>;
128}
129
130impl<T: SearchableItem> SearchableItemHandle for View<T> {
131    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
132        Box::new(self.downgrade())
133    }
134
135    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
136        Box::new(self.clone())
137    }
138
139    fn supported_options(&self) -> SearchOptions {
140        T::supported_options()
141    }
142
143    fn subscribe_to_search_events(
144        &self,
145        cx: &mut WindowContext,
146        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
147    ) -> Subscription {
148        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
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        count: usize,
182        cx: &mut WindowContext,
183    ) -> usize {
184        let matches = downcast_matches(matches);
185        self.update(cx, |this, cx| {
186            this.match_index_for_direction(&matches, current_index, direction, count, cx)
187        })
188    }
189    fn find_matches(
190        &self,
191        query: Arc<SearchQuery>,
192        cx: &mut WindowContext,
193    ) -> Task<Vec<Box<dyn Any + Send>>> {
194        let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
195        cx.spawn(|_| async {
196            let matches = matches.await;
197            matches
198                .into_iter()
199                .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
200                .collect()
201        })
202    }
203    fn active_match_index(
204        &self,
205        matches: &Vec<Box<dyn Any + Send>>,
206        cx: &mut WindowContext,
207    ) -> Option<usize> {
208        let matches = downcast_matches(matches);
209        self.update(cx, |this, cx| this.active_match_index(matches, cx))
210    }
211
212    fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) {
213        let matches = matches.downcast_ref().unwrap();
214        self.update(cx, |this, cx| this.replace(matches, query, cx))
215    }
216}
217
218fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
219    matches
220        .iter()
221        .map(|range| range.downcast_ref::<T>().cloned())
222        .collect::<Option<Vec<_>>>()
223        .expect(
224            "SearchableItemHandle function called with vec of matches of a different type than expected",
225        )
226}
227
228impl From<Box<dyn SearchableItemHandle>> for AnyView {
229    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
230        this.to_any().clone()
231    }
232}
233
234impl From<&Box<dyn SearchableItemHandle>> for AnyView {
235    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
236        this.to_any().clone()
237    }
238}
239
240impl PartialEq for Box<dyn SearchableItemHandle> {
241    fn eq(&self, other: &Self) -> bool {
242        self.item_id() == other.item_id()
243    }
244}
245
246impl Eq for Box<dyn SearchableItemHandle> {}
247
248pub trait WeakSearchableItemHandle: WeakItemHandle {
249    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
250
251    fn into_any(self) -> AnyWeakView;
252}
253
254impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
255    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
256        Some(Box::new(self.upgrade()?))
257    }
258
259    fn into_any(self) -> AnyWeakView {
260        self.into()
261    }
262}
263
264impl PartialEq for Box<dyn WeakSearchableItemHandle> {
265    fn eq(&self, other: &Self) -> bool {
266        self.id() == other.id()
267    }
268}
269
270impl Eq for Box<dyn WeakSearchableItemHandle> {}
271
272impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
273    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
274        self.id().hash(state)
275    }
276}