searchable.rs

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