1use std::{any::Any, sync::Arc};
2
3use any_vec::AnyVec;
4use gpui::{
5 AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
6 WeakView, WindowContext,
7};
8use project::search::SearchQuery;
9
10use crate::{
11 item::{Item, WeakItemHandle},
12 ItemHandle,
13};
14
15#[derive(Clone, Debug)]
16pub enum SearchEvent {
17 MatchesInvalidated,
18 ActiveMatchChanged,
19}
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
22pub enum Direction {
23 Prev,
24 #[default]
25 Next,
26}
27
28impl Direction {
29 pub fn opposite(&self) -> Self {
30 match self {
31 Direction::Prev => Direction::Next,
32 Direction::Next => Direction::Prev,
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, Default)]
38pub struct SearchOptions {
39 pub case: bool,
40 pub word: bool,
41 pub regex: bool,
42 /// Specifies whether the supports search & replace.
43 pub replacement: bool,
44 pub selection: bool,
45}
46
47pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
48 type Match: Any + Sync + Send + Clone;
49
50 fn supported_options() -> SearchOptions {
51 SearchOptions {
52 case: true,
53 word: true,
54 regex: true,
55 replacement: true,
56 selection: true,
57 }
58 }
59
60 fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {}
61
62 fn has_filtered_search_ranges(&mut self) -> bool {
63 Self::supported_options().selection
64 }
65
66 fn toggle_filtered_search_ranges(&mut self, _enabled: bool, _cx: &mut ViewContext<Self>) {}
67
68 fn get_matches(&self, _: &mut WindowContext) -> Vec<Self::Match> {
69 Vec::new()
70 }
71 fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
72 fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
73 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
74 fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
75 fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
76 fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
77 fn replace_all(
78 &mut self,
79 matches: &mut dyn Iterator<Item = &Self::Match>,
80 query: &SearchQuery,
81 cx: &mut ViewContext<Self>,
82 ) {
83 for item in matches {
84 self.replace(item, query, cx);
85 }
86 }
87 fn match_index_for_direction(
88 &mut self,
89 matches: &[Self::Match],
90 current_index: usize,
91 direction: Direction,
92 count: usize,
93 _: &mut ViewContext<Self>,
94 ) -> usize {
95 match direction {
96 Direction::Prev => {
97 let count = count % matches.len();
98 if current_index >= count {
99 current_index - count
100 } else {
101 matches.len() - (count - current_index)
102 }
103 }
104 Direction::Next => (current_index + count) % matches.len(),
105 }
106 }
107 fn find_matches(
108 &mut self,
109 query: Arc<SearchQuery>,
110 cx: &mut ViewContext<Self>,
111 ) -> Task<Vec<Self::Match>>;
112 fn active_match_index(
113 &mut self,
114 matches: &[Self::Match],
115 cx: &mut ViewContext<Self>,
116 ) -> Option<usize>;
117}
118
119pub trait SearchableItemHandle: ItemHandle {
120 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
121 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
122 fn supported_options(&self) -> SearchOptions;
123 fn subscribe_to_search_events(
124 &self,
125 cx: &mut WindowContext,
126 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
127 ) -> Subscription;
128 fn clear_matches(&self, cx: &mut WindowContext);
129 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
130 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
131 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
132 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
133 fn replace(
134 &self,
135 _: any_vec::element::ElementRef<'_, dyn Send>,
136 _: &SearchQuery,
137 _: &mut WindowContext,
138 );
139 fn replace_all(
140 &self,
141 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
142 query: &SearchQuery,
143 cx: &mut WindowContext,
144 );
145 fn match_index_for_direction(
146 &self,
147 matches: &AnyVec<dyn Send>,
148 current_index: usize,
149 direction: Direction,
150 count: usize,
151 cx: &mut WindowContext,
152 ) -> usize;
153 fn find_matches(
154 &self,
155 query: Arc<SearchQuery>,
156 cx: &mut WindowContext,
157 ) -> Task<AnyVec<dyn Send>>;
158 fn active_match_index(
159 &self,
160 matches: &AnyVec<dyn Send>,
161 cx: &mut WindowContext,
162 ) -> Option<usize>;
163 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
164
165 fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext);
166}
167
168impl<T: SearchableItem> SearchableItemHandle for View<T> {
169 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
170 Box::new(self.downgrade())
171 }
172
173 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
174 Box::new(self.clone())
175 }
176
177 fn supported_options(&self) -> SearchOptions {
178 T::supported_options()
179 }
180
181 fn subscribe_to_search_events(
182 &self,
183 cx: &mut WindowContext,
184 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
185 ) -> Subscription {
186 cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
187 }
188
189 fn clear_matches(&self, cx: &mut WindowContext) {
190 self.update(cx, |this, cx| this.clear_matches(cx));
191 }
192 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
193 let matches = matches.downcast_ref().unwrap();
194 self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
195 }
196 fn query_suggestion(&self, cx: &mut WindowContext) -> String {
197 self.update(cx, |this, cx| this.query_suggestion(cx))
198 }
199 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
200 let matches = matches.downcast_ref().unwrap();
201 self.update(cx, |this, cx| {
202 this.activate_match(index, matches.as_slice(), cx)
203 });
204 }
205
206 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
207 let matches = matches.downcast_ref().unwrap();
208 self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
209 }
210
211 fn match_index_for_direction(
212 &self,
213 matches: &AnyVec<dyn Send>,
214 current_index: usize,
215 direction: Direction,
216 count: usize,
217 cx: &mut WindowContext,
218 ) -> usize {
219 let matches = matches.downcast_ref().unwrap();
220 self.update(cx, |this, cx| {
221 this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
222 })
223 }
224 fn find_matches(
225 &self,
226 query: Arc<SearchQuery>,
227 cx: &mut WindowContext,
228 ) -> Task<AnyVec<dyn Send>> {
229 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
230 cx.spawn(|_| async {
231 let matches = matches.await;
232 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
233 {
234 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
235 for mat in matches {
236 any_matches.push(mat);
237 }
238 }
239 any_matches
240 })
241 }
242 fn active_match_index(
243 &self,
244 matches: &AnyVec<dyn Send>,
245 cx: &mut WindowContext,
246 ) -> Option<usize> {
247 let matches = matches.downcast_ref()?;
248 self.update(cx, |this, cx| {
249 this.active_match_index(matches.as_slice(), cx)
250 })
251 }
252
253 fn replace(
254 &self,
255 mat: any_vec::element::ElementRef<'_, dyn Send>,
256 query: &SearchQuery,
257 cx: &mut WindowContext,
258 ) {
259 let mat = mat.downcast_ref().unwrap();
260 self.update(cx, |this, cx| this.replace(mat, query, cx))
261 }
262
263 fn replace_all(
264 &self,
265 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
266 query: &SearchQuery,
267 cx: &mut WindowContext,
268 ) {
269 self.update(cx, |this, cx| {
270 this.replace_all(&mut matches.map(|m| m.downcast_ref().unwrap()), query, cx);
271 })
272 }
273
274 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
275 self.update(cx, |this, cx| {
276 this.search_bar_visibility_changed(visible, cx)
277 });
278 }
279
280 fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext) {
281 self.update(cx, |this, cx| {
282 this.toggle_filtered_search_ranges(enabled, cx)
283 });
284 }
285}
286
287impl From<Box<dyn SearchableItemHandle>> for AnyView {
288 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
289 this.to_any().clone()
290 }
291}
292
293impl From<&Box<dyn SearchableItemHandle>> for AnyView {
294 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
295 this.to_any().clone()
296 }
297}
298
299impl PartialEq for Box<dyn SearchableItemHandle> {
300 fn eq(&self, other: &Self) -> bool {
301 self.item_id() == other.item_id()
302 }
303}
304
305impl Eq for Box<dyn SearchableItemHandle> {}
306
307pub trait WeakSearchableItemHandle: WeakItemHandle {
308 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
309
310 fn into_any(self) -> AnyWeakView;
311}
312
313impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
314 fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
315 Some(Box::new(self.upgrade()?))
316 }
317
318 fn into_any(self) -> AnyWeakView {
319 self.into()
320 }
321}
322
323impl PartialEq for Box<dyn WeakSearchableItemHandle> {
324 fn eq(&self, other: &Self) -> bool {
325 self.id() == other.id()
326 }
327}
328
329impl Eq for Box<dyn WeakSearchableItemHandle> {}
330
331impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
332 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
333 self.id().hash(state)
334 }
335}