1use std::any::Any;
2
3use gpui::{
4 AnyViewHandle, AnyWeakViewHandle, AppContext, MutableAppContext, Subscription, Task,
5 ViewContext, ViewHandle, WeakViewHandle,
6};
7use project::search::SearchQuery;
8
9use crate::{Item, ItemHandle, WeakItemHandle};
10
11#[derive(Debug)]
12pub enum SearchEvent {
13 MatchesInvalidated,
14 ActiveMatchChanged,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq)]
18pub enum Direction {
19 Prev,
20 Next,
21}
22
23pub trait SearchableItem: Item {
24 type Match: Any + Sync + Send + Clone;
25
26 fn to_search_event(event: &Self::Event) -> Option<SearchEvent>;
27 fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
28 fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
29 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
30 fn activate_next_match(
31 &mut self,
32 index: usize,
33 direction: Direction,
34 matches: Vec<Self::Match>,
35 cx: &mut ViewContext<Self>,
36 );
37 fn activate_match_at_index(
38 &mut self,
39 index: usize,
40 matches: Vec<Self::Match>,
41 cx: &mut ViewContext<Self>,
42 );
43 fn find_matches(
44 &mut self,
45 query: SearchQuery,
46 cx: &mut ViewContext<Self>,
47 ) -> Task<Vec<Self::Match>>;
48 fn active_match_index(
49 &mut self,
50 matches: Vec<Self::Match>,
51 cx: &mut ViewContext<Self>,
52 ) -> Option<usize>;
53}
54
55pub trait SearchableItemHandle: ItemHandle {
56 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
57 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
58 fn subscribe(
59 &self,
60 cx: &mut MutableAppContext,
61 handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
62 ) -> Subscription;
63 fn clear_highlights(&self, cx: &mut MutableAppContext);
64 fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext);
65 fn query_suggestion(&self, cx: &mut MutableAppContext) -> String;
66 fn select_next_match_in_direction(
67 &self,
68 index: usize,
69 direction: Direction,
70 matches: &Vec<Box<dyn Any + Send>>,
71 cx: &mut MutableAppContext,
72 );
73 fn select_match_by_index(
74 &self,
75 index: usize,
76 matches: &Vec<Box<dyn Any + Send>>,
77 cx: &mut MutableAppContext,
78 );
79 fn matches(
80 &self,
81 query: SearchQuery,
82 cx: &mut MutableAppContext,
83 ) -> Task<Vec<Box<dyn Any + Send>>>;
84 fn active_match_index(
85 &self,
86 matches: &Vec<Box<dyn Any + Send>>,
87 cx: &mut MutableAppContext,
88 ) -> Option<usize>;
89}
90
91impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
92 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
93 Box::new(self.downgrade())
94 }
95
96 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
97 Box::new(self.clone())
98 }
99
100 fn subscribe(
101 &self,
102 cx: &mut MutableAppContext,
103 handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
104 ) -> Subscription {
105 cx.subscribe(self, move |_, event, cx| {
106 if let Some(search_event) = T::to_search_event(event) {
107 handler(search_event, cx)
108 }
109 })
110 }
111
112 fn clear_highlights(&self, cx: &mut MutableAppContext) {
113 self.update(cx, |this, cx| this.clear_matches(cx));
114 }
115 fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext) {
116 let matches = downcast_matches(matches);
117 self.update(cx, |this, cx| this.update_matches(matches, cx));
118 }
119 fn query_suggestion(&self, cx: &mut MutableAppContext) -> String {
120 self.update(cx, |this, cx| this.query_suggestion(cx))
121 }
122 fn select_next_match_in_direction(
123 &self,
124 index: usize,
125 direction: Direction,
126 matches: &Vec<Box<dyn Any + Send>>,
127 cx: &mut MutableAppContext,
128 ) {
129 let matches = downcast_matches(matches);
130 self.update(cx, |this, cx| {
131 this.activate_next_match(index, direction, matches, cx)
132 });
133 }
134 fn select_match_by_index(
135 &self,
136 index: usize,
137 matches: &Vec<Box<dyn Any + Send>>,
138 cx: &mut MutableAppContext,
139 ) {
140 let matches = downcast_matches(matches);
141 self.update(cx, |this, cx| {
142 this.activate_match_at_index(index, matches, cx)
143 });
144 }
145 fn matches(
146 &self,
147 query: SearchQuery,
148 cx: &mut MutableAppContext,
149 ) -> Task<Vec<Box<dyn Any + Send>>> {
150 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
151 cx.foreground().spawn(async {
152 let matches = matches.await;
153 matches
154 .into_iter()
155 .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
156 .collect()
157 })
158 }
159 fn active_match_index(
160 &self,
161 matches: &Vec<Box<dyn Any + Send>>,
162 cx: &mut MutableAppContext,
163 ) -> Option<usize> {
164 let matches = downcast_matches(matches);
165 self.update(cx, |this, cx| this.active_match_index(matches, cx))
166 }
167}
168
169fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
170 matches
171 .iter()
172 .map(|range| range.downcast_ref::<T>().cloned())
173 .collect::<Option<Vec<_>>>()
174 .expect(
175 "SearchableItemHandle function called with vec of matches of a different type than expected",
176 )
177}
178
179impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
180 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
181 this.to_any()
182 }
183}
184
185impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
186 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
187 this.to_any()
188 }
189}
190
191impl PartialEq for Box<dyn SearchableItemHandle> {
192 fn eq(&self, other: &Self) -> bool {
193 self.id() == other.id() && self.window_id() == other.window_id()
194 }
195}
196
197impl Eq for Box<dyn SearchableItemHandle> {}
198
199pub trait WeakSearchableItemHandle: WeakItemHandle {
200 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
201
202 fn to_any(self) -> AnyWeakViewHandle;
203}
204
205impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
206 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
207 Some(Box::new(self.upgrade(cx)?))
208 }
209
210 fn to_any(self) -> AnyWeakViewHandle {
211 self.into()
212 }
213}
214
215impl PartialEq for Box<dyn WeakSearchableItemHandle> {
216 fn eq(&self, other: &Self) -> bool {
217 self.id() == other.id() && self.window_id() == other.window_id()
218 }
219}
220
221impl Eq for Box<dyn WeakSearchableItemHandle> {}
222
223impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
224 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
225 (self.id(), self.window_id()).hash(state)
226 }
227}