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 current_index: usize,
59 direction: Direction,
60 count: usize,
61 _: &mut ViewContext<Self>,
62 ) -> usize {
63 match direction {
64 Direction::Prev => {
65 let count = count % matches.len();
66 if current_index >= count {
67 current_index - count
68 } else {
69 matches.len() - (count - current_index)
70 }
71 }
72 Direction::Next => (current_index + count) % matches.len(),
73 }
74 }
75 fn find_matches(
76 &mut self,
77 query: SearchQuery,
78 cx: &mut ViewContext<Self>,
79 ) -> Task<Vec<Self::Match>>;
80 fn active_match_index(
81 &mut self,
82 matches: Vec<Self::Match>,
83 cx: &mut ViewContext<Self>,
84 ) -> Option<usize>;
85}
86
87pub trait SearchableItemHandle: ItemHandle {
88 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
89 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
90 fn supported_options(&self) -> SearchOptions;
91 fn subscribe_to_search_events(
92 &self,
93 cx: &mut WindowContext,
94 handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
95 ) -> Subscription;
96 fn clear_matches(&self, cx: &mut WindowContext);
97 fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
98 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
99 fn activate_match(
100 &self,
101 index: usize,
102 matches: &Vec<Box<dyn Any + Send>>,
103 cx: &mut WindowContext,
104 );
105 fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
106 fn match_index_for_direction(
107 &self,
108 matches: &Vec<Box<dyn Any + Send>>,
109 current_index: usize,
110 direction: Direction,
111 count: usize,
112 cx: &mut WindowContext,
113 ) -> usize;
114 fn find_matches(
115 &self,
116 query: SearchQuery,
117 cx: &mut WindowContext,
118 ) -> Task<Vec<Box<dyn Any + Send>>>;
119 fn active_match_index(
120 &self,
121 matches: &Vec<Box<dyn Any + Send>>,
122 cx: &mut WindowContext,
123 ) -> Option<usize>;
124}
125
126impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
127 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
128 Box::new(self.downgrade())
129 }
130
131 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
132 Box::new(self.clone())
133 }
134
135 fn supported_options(&self) -> SearchOptions {
136 T::supported_options()
137 }
138
139 fn subscribe_to_search_events(
140 &self,
141 cx: &mut WindowContext,
142 handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
143 ) -> Subscription {
144 cx.subscribe(self, move |handle, event, cx| {
145 let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
146 if let Some(search_event) = search_event {
147 handler(search_event, cx)
148 }
149 })
150 }
151
152 fn clear_matches(&self, cx: &mut WindowContext) {
153 self.update(cx, |this, cx| this.clear_matches(cx));
154 }
155 fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
156 let matches = downcast_matches(matches);
157 self.update(cx, |this, cx| this.update_matches(matches, cx));
158 }
159 fn query_suggestion(&self, cx: &mut WindowContext) -> String {
160 self.update(cx, |this, cx| this.query_suggestion(cx))
161 }
162 fn activate_match(
163 &self,
164 index: usize,
165 matches: &Vec<Box<dyn Any + Send>>,
166 cx: &mut WindowContext,
167 ) {
168 let matches = downcast_matches(matches);
169 self.update(cx, |this, cx| this.activate_match(index, matches, cx));
170 }
171
172 fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
173 let matches = downcast_matches(matches);
174 self.update(cx, |this, cx| this.select_matches(matches, cx));
175 }
176
177 fn match_index_for_direction(
178 &self,
179 matches: &Vec<Box<dyn Any + Send>>,
180 current_index: usize,
181 direction: Direction,
182 count: usize,
183 cx: &mut WindowContext,
184 ) -> usize {
185 let matches = downcast_matches(matches);
186 self.update(cx, |this, cx| {
187 this.match_index_for_direction(&matches, current_index, direction, count, cx)
188 })
189 }
190 fn find_matches(
191 &self,
192 query: SearchQuery,
193 cx: &mut WindowContext,
194 ) -> Task<Vec<Box<dyn Any + Send>>> {
195 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
196 cx.foreground().spawn(async {
197 let matches = matches.await;
198 matches
199 .into_iter()
200 .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
201 .collect()
202 })
203 }
204 fn active_match_index(
205 &self,
206 matches: &Vec<Box<dyn Any + Send>>,
207 cx: &mut WindowContext,
208 ) -> Option<usize> {
209 let matches = downcast_matches(matches);
210 self.update(cx, |this, cx| this.active_match_index(matches, cx))
211 }
212}
213
214fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
215 matches
216 .iter()
217 .map(|range| range.downcast_ref::<T>().cloned())
218 .collect::<Option<Vec<_>>>()
219 .expect(
220 "SearchableItemHandle function called with vec of matches of a different type than expected",
221 )
222}
223
224impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
225 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
226 this.as_any().clone()
227 }
228}
229
230impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
231 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
232 this.as_any().clone()
233 }
234}
235
236impl PartialEq for Box<dyn SearchableItemHandle> {
237 fn eq(&self, other: &Self) -> bool {
238 self.id() == other.id() && self.window() == other.window()
239 }
240}
241
242impl Eq for Box<dyn SearchableItemHandle> {}
243
244pub trait WeakSearchableItemHandle: WeakItemHandle {
245 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
246
247 fn into_any(self) -> AnyWeakViewHandle;
248}
249
250impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
251 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
252 Some(Box::new(self.upgrade(cx)?))
253 }
254
255 fn into_any(self) -> AnyWeakViewHandle {
256 self.into_any()
257 }
258}
259
260impl PartialEq for Box<dyn WeakSearchableItemHandle> {
261 fn eq(&self, other: &Self) -> bool {
262 self.id() == other.id() && self.window() == other.window()
263 }
264}
265
266impl Eq for Box<dyn WeakSearchableItemHandle> {}
267
268impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
269 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
270 (self.id(), self.window().id()).hash(state)
271 }
272}