use std::any::Any; use gpui::{ AnyViewHandle, AnyWeakViewHandle, AppContext, MutableAppContext, Subscription, Task, ViewContext, ViewHandle, WeakViewHandle, }; use project::search::SearchQuery; use crate::{item::WeakItemHandle, Item, ItemHandle}; #[derive(Debug)] pub enum SearchEvent { MatchesInvalidated, ActiveMatchChanged, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Direction { Prev, Next, } #[derive(Clone, Copy, Debug, Default)] pub struct SearchOptions { pub case: bool, pub word: bool, pub regex: bool, } pub trait SearchableItem: Item { type Match: Any + Sync + Send + Clone; fn supported_options() -> SearchOptions { SearchOptions { case: true, word: true, regex: true, } } fn to_search_event(event: &Self::Event) -> Option; fn clear_matches(&mut self, cx: &mut ViewContext); fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; fn activate_match( &mut self, index: usize, matches: Vec, cx: &mut ViewContext, ); fn match_index_for_direction( &mut self, matches: &Vec, mut current_index: usize, direction: Direction, _: &mut ViewContext, ) -> usize { match direction { Direction::Prev => { if current_index == 0 { matches.len() - 1 } else { current_index - 1 } } Direction::Next => { current_index += 1; if current_index == matches.len() { 0 } else { current_index } } } } fn find_matches( &mut self, query: SearchQuery, cx: &mut ViewContext, ) -> Task>; fn active_match_index( &mut self, matches: Vec, cx: &mut ViewContext, ) -> Option; } pub trait SearchableItemHandle: ItemHandle { fn downgrade(&self) -> Box; fn boxed_clone(&self) -> Box; fn supported_options(&self) -> SearchOptions; fn subscribe_to_search_events( &self, cx: &mut MutableAppContext, handler: Box, ) -> Subscription; fn clear_matches(&self, cx: &mut MutableAppContext); fn update_matches(&self, matches: &Vec>, cx: &mut MutableAppContext); fn query_suggestion(&self, cx: &mut MutableAppContext) -> String; fn activate_match( &self, index: usize, matches: &Vec>, cx: &mut MutableAppContext, ); fn match_index_for_direction( &self, matches: &Vec>, current_index: usize, direction: Direction, cx: &mut MutableAppContext, ) -> usize; fn find_matches( &self, query: SearchQuery, cx: &mut MutableAppContext, ) -> Task>>; fn active_match_index( &self, matches: &Vec>, cx: &mut MutableAppContext, ) -> Option; } impl SearchableItemHandle for ViewHandle { fn downgrade(&self) -> Box { Box::new(self.downgrade()) } fn boxed_clone(&self) -> Box { Box::new(self.clone()) } fn supported_options(&self) -> SearchOptions { T::supported_options() } fn subscribe_to_search_events( &self, cx: &mut MutableAppContext, handler: Box, ) -> Subscription { cx.subscribe(self, move |_, event, cx| { if let Some(search_event) = T::to_search_event(event) { handler(search_event, cx) } }) } fn clear_matches(&self, cx: &mut MutableAppContext) { self.update(cx, |this, cx| this.clear_matches(cx)); } fn update_matches(&self, matches: &Vec>, cx: &mut MutableAppContext) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.update_matches(matches, cx)); } fn query_suggestion(&self, cx: &mut MutableAppContext) -> String { self.update(cx, |this, cx| this.query_suggestion(cx)) } fn activate_match( &self, index: usize, matches: &Vec>, cx: &mut MutableAppContext, ) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.activate_match(index, matches, cx)); } fn match_index_for_direction( &self, matches: &Vec>, current_index: usize, direction: Direction, cx: &mut MutableAppContext, ) -> usize { let matches = downcast_matches(matches); self.update(cx, |this, cx| { this.match_index_for_direction(&matches, current_index, direction, cx) }) } fn find_matches( &self, query: SearchQuery, cx: &mut MutableAppContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); cx.foreground().spawn(async { let matches = matches.await; matches .into_iter() .map::, _>(|range| Box::new(range)) .collect() }) } fn active_match_index( &self, matches: &Vec>, cx: &mut MutableAppContext, ) -> Option { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.active_match_index(matches, cx)) } } fn downcast_matches(matches: &Vec>) -> Vec { matches .iter() .map(|range| range.downcast_ref::().cloned()) .collect::>>() .expect( "SearchableItemHandle function called with vec of matches of a different type than expected", ) } impl From> for AnyViewHandle { fn from(this: Box) -> Self { this.to_any() } } impl From<&Box> for AnyViewHandle { fn from(this: &Box) -> Self { this.to_any() } } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { self.id() == other.id() && self.window_id() == other.window_id() } } impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; fn to_any(self) -> AnyWeakViewHandle; } impl WeakSearchableItemHandle for WeakViewHandle { fn upgrade(&self, cx: &AppContext) -> Option> { Some(Box::new(self.upgrade(cx)?)) } fn to_any(self) -> AnyWeakViewHandle { self.into() } } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { self.id() == other.id() && self.window_id() == other.window_id() } } impl Eq for Box {} impl std::hash::Hash for Box { fn hash(&self, state: &mut H) { (self.id(), self.window_id()).hash(state) } }