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