use std::{any::Any, sync::Arc}; use any_vec::AnyVec; use gpui::{ AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView, WindowContext, }; use project::search::SearchQuery; use crate::{ item::{Item, WeakItemHandle}, ItemHandle, }; #[derive(Clone, Debug)] pub enum SearchEvent { MatchesInvalidated, ActiveMatchChanged, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum Direction { Prev, #[default] Next, } impl Direction { pub fn opposite(&self) -> Self { match self { Direction::Prev => Direction::Next, Direction::Next => Direction::Prev, } } } #[derive(Clone, Copy, Debug, Default)] pub struct SearchOptions { pub case: bool, pub word: bool, pub regex: bool, /// Specifies whether the supports search & replace. pub replacement: bool, pub selection: bool, } pub trait SearchableItem: Item + EventEmitter { type Match: Any + Sync + Send + Clone; fn supported_options() -> SearchOptions { SearchOptions { case: true, word: true, regex: true, replacement: true, selection: true, } } fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext) {} fn has_filtered_search_ranges(&mut self) -> bool { Self::supported_options().selection } fn toggle_filtered_search_ranges(&mut self, _enabled: bool, _cx: &mut ViewContext) {} fn clear_matches(&mut self, cx: &mut ViewContext); fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext); fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext); fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext); fn replace_all( &mut self, matches: &mut dyn Iterator, query: &SearchQuery, cx: &mut ViewContext, ) { for item in matches { self.replace(item, query, cx); } } fn match_index_for_direction( &mut self, matches: &[Self::Match], 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: &[Self::Match], 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: &AnyVec, cx: &mut WindowContext); fn query_suggestion(&self, cx: &mut WindowContext) -> String; fn activate_match(&self, index: usize, matches: &AnyVec, cx: &mut WindowContext); fn select_matches(&self, matches: &AnyVec, cx: &mut WindowContext); fn replace( &self, _: any_vec::element::ElementRef<'_, dyn Send>, _: &SearchQuery, _: &mut WindowContext, ); fn replace_all( &self, matches: &mut dyn Iterator>, query: &SearchQuery, cx: &mut WindowContext, ); fn match_index_for_direction( &self, matches: &AnyVec, 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: &AnyVec, cx: &mut WindowContext, ) -> Option; fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext); fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext); } impl SearchableItemHandle for View { 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 WindowContext, handler: Box, ) -> Subscription { cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx)) } fn clear_matches(&self, cx: &mut WindowContext) { self.update(cx, |this, cx| this.clear_matches(cx)); } fn update_matches(&self, matches: &AnyVec, cx: &mut WindowContext) { let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| this.update_matches(matches.as_slice(), 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: &AnyVec, cx: &mut WindowContext) { let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| { this.activate_match(index, matches.as_slice(), cx) }); } fn select_matches(&self, matches: &AnyVec, cx: &mut WindowContext) { let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx)); } fn match_index_for_direction( &self, matches: &AnyVec, current_index: usize, direction: Direction, count: usize, cx: &mut WindowContext, ) -> usize { let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| { this.match_index_for_direction(matches.as_slice(), 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(|_| async { let matches = matches.await; let mut any_matches = AnyVec::with_capacity::(matches.len()); { let mut any_matches = any_matches.downcast_mut::().unwrap(); for mat in matches { any_matches.push(mat); } } any_matches }) } fn active_match_index( &self, matches: &AnyVec, cx: &mut WindowContext, ) -> Option { let matches = matches.downcast_ref()?; self.update(cx, |this, cx| { this.active_match_index(matches.as_slice(), cx) }) } fn replace( &self, mat: any_vec::element::ElementRef<'_, dyn Send>, query: &SearchQuery, cx: &mut WindowContext, ) { let mat = mat.downcast_ref().unwrap(); self.update(cx, |this, cx| this.replace(mat, query, cx)) } fn replace_all( &self, matches: &mut dyn Iterator>, query: &SearchQuery, cx: &mut WindowContext, ) { self.update(cx, |this, cx| { this.replace_all(&mut matches.map(|m| m.downcast_ref().unwrap()), query, cx); }) } fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| { this.search_bar_visibility_changed(visible, cx) }); } fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| { this.toggle_filtered_search_ranges(enabled, cx) }); } } 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.item_id() == other.item_id() } } impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; fn into_any(self) -> AnyWeakView; } impl WeakSearchableItemHandle for WeakView { fn upgrade(&self, _cx: &AppContext) -> Option> { Some(Box::new(self.upgrade()?)) } fn into_any(self) -> AnyWeakView { self.into() } } 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) } }