use std::{any::Any, sync::Arc}; use gpui2::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext}; use project2::search::SearchQuery; use crate::{ item::{Item, WeakItemHandle}, 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, /// Specifies whether the item supports search & replace. pub replacement: bool, } pub trait SearchableItem: Item { type Match: Any + Sync + Send + Clone; fn supported_options() -> SearchOptions { SearchOptions { case: true, word: true, regex: true, replacement: true, } } fn to_search_event( &mut self, event: &Self::Event, cx: &mut ViewContext, ) -> 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 select_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext); fn match_index_for_direction( &mut self, matches: &Vec, current_index: usize, direction: Direction, count: usize, _: &mut ViewContext, ) -> usize { match direction { Direction::Prev => { let count = count % matches.len(); if current_index >= count { current_index - count } else { matches.len() - (count - current_index) } } Direction::Next => (current_index + count) % matches.len(), } } fn find_matches( &mut self, query: Arc, 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 WindowContext, handler: Box, ) -> Subscription; fn clear_matches(&self, cx: &mut WindowContext); fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext); fn query_suggestion(&self, cx: &mut WindowContext) -> String; fn activate_match( &self, index: usize, matches: &Vec>, cx: &mut WindowContext, ); fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext); fn replace(&self, _: &Box, _: &SearchQuery, _: &mut WindowContext); fn match_index_for_direction( &self, matches: &Vec>, current_index: usize, direction: Direction, count: usize, cx: &mut WindowContext, ) -> usize; fn find_matches( &self, query: Arc, cx: &mut WindowContext, ) -> Task>>; fn active_match_index( &self, matches: &Vec>, cx: &mut WindowContext, ) -> Option; } impl SearchableItemHandle for View { fn downgrade(&self) -> Box { // Box::new(self.downgrade()) todo!() } 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 WindowContext, handler: Box, ) -> Subscription { cx.subscribe(self, move |handle, event, cx| { let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx)); if let Some(search_event) = search_event { handler(search_event, cx) } }) } fn clear_matches(&self, cx: &mut WindowContext) { self.update(cx, |this, cx| this.clear_matches(cx)); } fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.update_matches(matches, cx)); } fn query_suggestion(&self, cx: &mut WindowContext) -> String { self.update(cx, |this, cx| this.query_suggestion(cx)) } fn activate_match( &self, index: usize, matches: &Vec>, cx: &mut WindowContext, ) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.activate_match(index, matches, cx)); } fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.select_matches(matches, cx)); } fn match_index_for_direction( &self, matches: &Vec>, current_index: usize, direction: Direction, count: usize, cx: &mut WindowContext, ) -> usize { let matches = downcast_matches(matches); self.update(cx, |this, cx| { this.match_index_for_direction(&matches, current_index, direction, count, cx) }) } fn find_matches( &self, query: Arc, cx: &mut WindowContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); cx.spawn_on_main(|cx| async { let matches = matches.await; matches .into_iter() .map::, _>(|range| Box::new(range)) .collect() }) } fn active_match_index( &self, matches: &Vec>, cx: &mut WindowContext, ) -> Option { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.active_match_index(matches, cx)) } fn replace(&self, matches: &Box, query: &SearchQuery, cx: &mut WindowContext) { let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| this.replace(matches, query, 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 AnyView { fn from(this: Box) -> Self { this.to_any().clone() } } impl From<&Box> for AnyView { fn from(this: &Box) -> Self { this.to_any().clone() } } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; // fn into_any(self) -> AnyWeakView; } // todo!() // impl WeakSearchableItemHandle for WeakView { // fn upgrade(&self, cx: &AppContext) -> Option> { // Some(Box::new(self.upgrade(cx)?)) // } // // fn into_any(self) -> AnyView { // // self.into_any() // // } // } impl PartialEq for Box { fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } impl Eq for Box {} impl std::hash::Hash for Box { fn hash(&self, state: &mut H) { self.id().hash(state) } }