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 clear_matches(&mut self, cx: &mut ViewContext<Self>);
69 fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
70 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
71 fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
72 fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
73 fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
74 fn replace_all(
75 &mut self,
76 matches: &mut dyn Iterator<Item = &Self::Match>,
77 query: &SearchQuery,
78 cx: &mut ViewContext<Self>,
79 ) {
80 for item in matches {
81 self.replace(item, query, cx);
82 }
83 }
84 fn match_index_for_direction(
85 &mut self,
86 matches: &[Self::Match],
87 current_index: usize,
88 direction: Direction,
89 count: usize,
90 _: &mut ViewContext<Self>,
91 ) -> usize {
92 match direction {
93 Direction::Prev => {
94 let count = count % matches.len();
95 if current_index >= count {
96 current_index - count
97 } else {
98 matches.len() - (count - current_index)
99 }
100 }
101 Direction::Next => (current_index + count) % matches.len(),
102 }
103 }
104 fn find_matches(
105 &mut self,
106 query: Arc<SearchQuery>,
107 cx: &mut ViewContext<Self>,
108 ) -> Task<Vec<Self::Match>>;
109 fn active_match_index(
110 &mut self,
111 matches: &[Self::Match],
112 cx: &mut ViewContext<Self>,
113 ) -> Option<usize>;
114}
115
116pub trait SearchableItemHandle: ItemHandle {
117 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
118 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
119 fn supported_options(&self) -> SearchOptions;
120 fn subscribe_to_search_events(
121 &self,
122 cx: &mut WindowContext,
123 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
124 ) -> Subscription;
125 fn clear_matches(&self, cx: &mut WindowContext);
126 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
127 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
128 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
129 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
130 fn replace(
131 &self,
132 _: any_vec::element::ElementRef<'_, dyn Send>,
133 _: &SearchQuery,
134 _: &mut WindowContext,
135 );
136 fn replace_all(
137 &self,
138 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
139 query: &SearchQuery,
140 cx: &mut WindowContext,
141 );
142 fn match_index_for_direction(
143 &self,
144 matches: &AnyVec<dyn Send>,
145 current_index: usize,
146 direction: Direction,
147 count: usize,
148 cx: &mut WindowContext,
149 ) -> usize;
150 fn find_matches(
151 &self,
152 query: Arc<SearchQuery>,
153 cx: &mut WindowContext,
154 ) -> Task<AnyVec<dyn Send>>;
155 fn active_match_index(
156 &self,
157 matches: &AnyVec<dyn Send>,
158 cx: &mut WindowContext,
159 ) -> Option<usize>;
160 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
161
162 fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext);
163}
164
165impl<T: SearchableItem> SearchableItemHandle for View<T> {
166 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
167 Box::new(self.downgrade())
168 }
169
170 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
171 Box::new(self.clone())
172 }
173
174 fn supported_options(&self) -> SearchOptions {
175 T::supported_options()
176 }
177
178 fn subscribe_to_search_events(
179 &self,
180 cx: &mut WindowContext,
181 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
182 ) -> Subscription {
183 cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
184 }
185
186 fn clear_matches(&self, cx: &mut WindowContext) {
187 self.update(cx, |this, cx| this.clear_matches(cx));
188 }
189 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
190 let matches = matches.downcast_ref().unwrap();
191 self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
192 }
193 fn query_suggestion(&self, cx: &mut WindowContext) -> String {
194 self.update(cx, |this, cx| this.query_suggestion(cx))
195 }
196 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
197 let matches = matches.downcast_ref().unwrap();
198 self.update(cx, |this, cx| {
199 this.activate_match(index, matches.as_slice(), cx)
200 });
201 }
202
203 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
204 let matches = matches.downcast_ref().unwrap();
205 self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
206 }
207
208 fn match_index_for_direction(
209 &self,
210 matches: &AnyVec<dyn Send>,
211 current_index: usize,
212 direction: Direction,
213 count: usize,
214 cx: &mut WindowContext,
215 ) -> usize {
216 let matches = matches.downcast_ref().unwrap();
217 self.update(cx, |this, cx| {
218 this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
219 })
220 }
221 fn find_matches(
222 &self,
223 query: Arc<SearchQuery>,
224 cx: &mut WindowContext,
225 ) -> Task<AnyVec<dyn Send>> {
226 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
227 cx.spawn(|_| async {
228 let matches = matches.await;
229 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
230 {
231 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
232 for mat in matches {
233 any_matches.push(mat);
234 }
235 }
236 any_matches
237 })
238 }
239 fn active_match_index(
240 &self,
241 matches: &AnyVec<dyn Send>,
242 cx: &mut WindowContext,
243 ) -> Option<usize> {
244 let matches = matches.downcast_ref()?;
245 self.update(cx, |this, cx| {
246 this.active_match_index(matches.as_slice(), cx)
247 })
248 }
249
250 fn replace(
251 &self,
252 mat: any_vec::element::ElementRef<'_, dyn Send>,
253 query: &SearchQuery,
254 cx: &mut WindowContext,
255 ) {
256 let mat = mat.downcast_ref().unwrap();
257 self.update(cx, |this, cx| this.replace(mat, query, cx))
258 }
259
260 fn replace_all(
261 &self,
262 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
263 query: &SearchQuery,
264 cx: &mut WindowContext,
265 ) {
266 self.update(cx, |this, cx| {
267 this.replace_all(&mut matches.map(|m| m.downcast_ref().unwrap()), query, cx);
268 })
269 }
270
271 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
272 self.update(cx, |this, cx| {
273 this.search_bar_visibility_changed(visible, cx)
274 });
275 }
276
277 fn toggle_filtered_search_ranges(&mut self, enabled: bool, cx: &mut WindowContext) {
278 self.update(cx, |this, cx| {
279 this.toggle_filtered_search_ranges(enabled, cx)
280 });
281 }
282}
283
284impl From<Box<dyn SearchableItemHandle>> for AnyView {
285 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
286 this.to_any().clone()
287 }
288}
289
290impl From<&Box<dyn SearchableItemHandle>> for AnyView {
291 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
292 this.to_any().clone()
293 }
294}
295
296impl PartialEq for Box<dyn SearchableItemHandle> {
297 fn eq(&self, other: &Self) -> bool {
298 self.item_id() == other.item_id()
299 }
300}
301
302impl Eq for Box<dyn SearchableItemHandle> {}
303
304pub trait WeakSearchableItemHandle: WeakItemHandle {
305 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
306
307 fn into_any(self) -> AnyWeakView;
308}
309
310impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
311 fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
312 Some(Box::new(self.upgrade()?))
313 }
314
315 fn into_any(self) -> AnyWeakView {
316 self.into()
317 }
318}
319
320impl PartialEq for Box<dyn WeakSearchableItemHandle> {
321 fn eq(&self, other: &Self) -> bool {
322 self.id() == other.id()
323 }
324}
325
326impl Eq for Box<dyn WeakSearchableItemHandle> {}
327
328impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
329 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
330 self.id().hash(state)
331 }
332}