1use std::{any::Any, sync::Arc};
2
3use any_vec::AnyVec;
4use gpui::{
5 AnyView, AnyWeakEntity, App, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
6 Window,
7};
8use project::search::SearchQuery;
9
10use crate::{
11 ItemHandle,
12 item::{Item, WeakItemHandle},
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 pub find_in_results: bool,
46}
47
48pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
49 type Match: Any + Sync + Send + Clone;
50
51 fn supported_options(&self) -> SearchOptions {
52 SearchOptions {
53 case: true,
54 word: true,
55 regex: true,
56 replacement: true,
57 selection: true,
58 find_in_results: false,
59 }
60 }
61
62 fn search_bar_visibility_changed(
63 &mut self,
64 _visible: bool,
65 _window: &mut Window,
66 _cx: &mut Context<Self>,
67 ) {
68 }
69
70 fn has_filtered_search_ranges(&mut self) -> bool {
71 self.supported_options().selection
72 }
73
74 fn toggle_filtered_search_ranges(
75 &mut self,
76 _enabled: bool,
77 _window: &mut Window,
78 _cx: &mut Context<Self>,
79 ) {
80 }
81
82 fn get_matches(&self, _window: &mut Window, _: &mut App) -> Vec<Self::Match> {
83 Vec::new()
84 }
85 fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
86 fn update_matches(
87 &mut self,
88 matches: &[Self::Match],
89 window: &mut Window,
90 cx: &mut Context<Self>,
91 );
92 fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
93 fn activate_match(
94 &mut self,
95 index: usize,
96 matches: &[Self::Match],
97 window: &mut Window,
98 cx: &mut Context<Self>,
99 );
100 fn select_matches(
101 &mut self,
102 matches: &[Self::Match],
103 window: &mut Window,
104 cx: &mut Context<Self>,
105 );
106 fn replace(
107 &mut self,
108 _: &Self::Match,
109 _: &SearchQuery,
110 _window: &mut Window,
111 _: &mut Context<Self>,
112 );
113 fn replace_all(
114 &mut self,
115 matches: &mut dyn Iterator<Item = &Self::Match>,
116 query: &SearchQuery,
117 window: &mut Window,
118 cx: &mut Context<Self>,
119 ) {
120 for item in matches {
121 self.replace(item, query, window, cx);
122 }
123 }
124 fn match_index_for_direction(
125 &mut self,
126 matches: &[Self::Match],
127 current_index: usize,
128 direction: Direction,
129 count: usize,
130 _window: &mut Window,
131 _: &mut Context<Self>,
132 ) -> usize {
133 match direction {
134 Direction::Prev => {
135 let count = count % matches.len();
136 if current_index >= count {
137 current_index - count
138 } else {
139 matches.len() - (count - current_index)
140 }
141 }
142 Direction::Next => (current_index + count) % matches.len(),
143 }
144 }
145 fn find_matches(
146 &mut self,
147 query: Arc<SearchQuery>,
148 window: &mut Window,
149 cx: &mut Context<Self>,
150 ) -> Task<Vec<Self::Match>>;
151 fn active_match_index(
152 &mut self,
153 direction: Direction,
154 matches: &[Self::Match],
155 window: &mut Window,
156 cx: &mut Context<Self>,
157 ) -> Option<usize>;
158}
159
160pub trait SearchableItemHandle: ItemHandle {
161 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
162 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
163 fn supported_options(&self, cx: &App) -> SearchOptions;
164 fn subscribe_to_search_events(
165 &self,
166 window: &mut Window,
167 cx: &mut App,
168 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
169 ) -> Subscription;
170 fn clear_matches(&self, window: &mut Window, cx: &mut App);
171 fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
172 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
173 fn activate_match(
174 &self,
175 index: usize,
176 matches: &AnyVec<dyn Send>,
177 window: &mut Window,
178 cx: &mut App,
179 );
180 fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
181 fn replace(
182 &self,
183 _: any_vec::element::ElementRef<'_, dyn Send>,
184 _: &SearchQuery,
185 _window: &mut Window,
186 _: &mut App,
187 );
188 fn replace_all(
189 &self,
190 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
191 query: &SearchQuery,
192 window: &mut Window,
193 cx: &mut App,
194 );
195 fn match_index_for_direction(
196 &self,
197 matches: &AnyVec<dyn Send>,
198 current_index: usize,
199 direction: Direction,
200 count: usize,
201 window: &mut Window,
202 cx: &mut App,
203 ) -> usize;
204 fn find_matches(
205 &self,
206 query: Arc<SearchQuery>,
207 window: &mut Window,
208 cx: &mut App,
209 ) -> Task<AnyVec<dyn Send>>;
210 fn active_match_index(
211 &self,
212 direction: Direction,
213 matches: &AnyVec<dyn Send>,
214 window: &mut Window,
215 cx: &mut App,
216 ) -> Option<usize>;
217 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
218
219 fn toggle_filtered_search_ranges(&mut self, enabled: bool, window: &mut Window, cx: &mut App);
220}
221
222impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
223 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
224 Box::new(self.downgrade())
225 }
226
227 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
228 Box::new(self.clone())
229 }
230
231 fn supported_options(&self, cx: &App) -> SearchOptions {
232 self.read(cx).supported_options()
233 }
234
235 fn subscribe_to_search_events(
236 &self,
237 window: &mut Window,
238 cx: &mut App,
239 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
240 ) -> Subscription {
241 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
242 handler(event, window, cx)
243 })
244 }
245
246 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
247 self.update(cx, |this, cx| this.clear_matches(window, cx));
248 }
249 fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
250 let matches = matches.downcast_ref().unwrap();
251 self.update(cx, |this, cx| {
252 this.update_matches(matches.as_slice(), window, cx)
253 });
254 }
255 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
256 self.update(cx, |this, cx| this.query_suggestion(window, cx))
257 }
258 fn activate_match(
259 &self,
260 index: usize,
261 matches: &AnyVec<dyn Send>,
262 window: &mut Window,
263 cx: &mut App,
264 ) {
265 let matches = matches.downcast_ref().unwrap();
266 self.update(cx, |this, cx| {
267 this.activate_match(index, matches.as_slice(), window, cx)
268 });
269 }
270
271 fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
272 let matches = matches.downcast_ref().unwrap();
273 self.update(cx, |this, cx| {
274 this.select_matches(matches.as_slice(), window, cx)
275 });
276 }
277
278 fn match_index_for_direction(
279 &self,
280 matches: &AnyVec<dyn Send>,
281 current_index: usize,
282 direction: Direction,
283 count: usize,
284 window: &mut Window,
285 cx: &mut App,
286 ) -> usize {
287 let matches = matches.downcast_ref().unwrap();
288 self.update(cx, |this, cx| {
289 this.match_index_for_direction(
290 matches.as_slice(),
291 current_index,
292 direction,
293 count,
294 window,
295 cx,
296 )
297 })
298 }
299 fn find_matches(
300 &self,
301 query: Arc<SearchQuery>,
302 window: &mut Window,
303 cx: &mut App,
304 ) -> Task<AnyVec<dyn Send>> {
305 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
306 window.spawn(cx, async |_| {
307 let matches = matches.await;
308 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
309 {
310 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
311 for mat in matches {
312 any_matches.push(mat);
313 }
314 }
315 any_matches
316 })
317 }
318 fn active_match_index(
319 &self,
320 direction: Direction,
321 matches: &AnyVec<dyn Send>,
322 window: &mut Window,
323 cx: &mut App,
324 ) -> Option<usize> {
325 let matches = matches.downcast_ref()?;
326 self.update(cx, |this, cx| {
327 this.active_match_index(direction, matches.as_slice(), window, cx)
328 })
329 }
330
331 fn replace(
332 &self,
333 mat: any_vec::element::ElementRef<'_, dyn Send>,
334 query: &SearchQuery,
335 window: &mut Window,
336 cx: &mut App,
337 ) {
338 let mat = mat.downcast_ref().unwrap();
339 self.update(cx, |this, cx| this.replace(mat, query, window, cx))
340 }
341
342 fn replace_all(
343 &self,
344 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
345 query: &SearchQuery,
346 window: &mut Window,
347 cx: &mut App,
348 ) {
349 self.update(cx, |this, cx| {
350 this.replace_all(
351 &mut matches.map(|m| m.downcast_ref().unwrap()),
352 query,
353 window,
354 cx,
355 );
356 })
357 }
358
359 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
360 self.update(cx, |this, cx| {
361 this.search_bar_visibility_changed(visible, window, cx)
362 });
363 }
364
365 fn toggle_filtered_search_ranges(&mut self, enabled: bool, window: &mut Window, cx: &mut App) {
366 self.update(cx, |this, cx| {
367 this.toggle_filtered_search_ranges(enabled, window, cx)
368 });
369 }
370}
371
372impl From<Box<dyn SearchableItemHandle>> for AnyView {
373 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
374 this.to_any()
375 }
376}
377
378impl From<&Box<dyn SearchableItemHandle>> for AnyView {
379 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
380 this.to_any()
381 }
382}
383
384impl PartialEq for Box<dyn SearchableItemHandle> {
385 fn eq(&self, other: &Self) -> bool {
386 self.item_id() == other.item_id()
387 }
388}
389
390impl Eq for Box<dyn SearchableItemHandle> {}
391
392pub trait WeakSearchableItemHandle: WeakItemHandle {
393 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
394
395 fn into_any(self) -> AnyWeakEntity;
396}
397
398impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
399 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
400 Some(Box::new(self.upgrade()?))
401 }
402
403 fn into_any(self) -> AnyWeakEntity {
404 self.into()
405 }
406}
407
408impl PartialEq for Box<dyn WeakSearchableItemHandle> {
409 fn eq(&self, other: &Self) -> bool {
410 self.id() == other.id()
411 }
412}
413
414impl Eq for Box<dyn WeakSearchableItemHandle> {}
415
416impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
417 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
418 self.id().hash(state)
419 }
420}