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