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(Copy, Clone, Debug, Default, PartialEq, Eq)]
16pub struct SearchToken(u64);
17
18impl SearchToken {
19 pub fn new(value: u64) -> Self {
20 Self(value)
21 }
22
23 pub fn value(&self) -> u64 {
24 self.0
25 }
26}
27
28#[derive(Clone, Debug)]
29pub enum CollapseDirection {
30 Collapsed,
31 Expanded,
32}
33
34#[derive(Debug, Clone)]
35pub enum SearchEvent {
36 MatchesInvalidated,
37 ActiveMatchChanged,
38 ResultsCollapsedChanged(CollapseDirection),
39}
40
41#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
42pub enum Direction {
43 Prev,
44 #[default]
45 Next,
46}
47
48impl Direction {
49 pub fn opposite(&self) -> Self {
50 match self {
51 Direction::Prev => Direction::Next,
52 Direction::Next => Direction::Prev,
53 }
54 }
55}
56
57#[derive(Clone, Copy, Debug, Default)]
58pub struct SearchOptions {
59 pub case: bool,
60 pub word: bool,
61 pub regex: bool,
62 /// Specifies whether the supports search & replace.
63 pub replacement: bool,
64 pub selection: bool,
65 pub find_in_results: bool,
66}
67
68// Whether to always select the current selection (even if empty)
69// or to use the default (restoring the previous search ranges if some,
70// otherwise using the whole file).
71#[derive(Clone, Copy, Debug, Default, PartialEq)]
72pub enum FilteredSearchRange {
73 Selection,
74 #[default]
75 Default,
76}
77
78pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
79 type Match: Any + Sync + Send + Clone;
80
81 fn supported_options(&self) -> SearchOptions {
82 SearchOptions {
83 case: true,
84 word: true,
85 regex: true,
86 replacement: true,
87 selection: true,
88 find_in_results: false,
89 }
90 }
91
92 fn search_bar_visibility_changed(
93 &mut self,
94 _visible: bool,
95 _window: &mut Window,
96 _cx: &mut Context<Self>,
97 ) {
98 }
99
100 fn has_filtered_search_ranges(&mut self) -> bool {
101 self.supported_options().selection
102 }
103
104 fn toggle_filtered_search_ranges(
105 &mut self,
106 _enabled: Option<FilteredSearchRange>,
107 _window: &mut Window,
108 _cx: &mut Context<Self>,
109 ) {
110 }
111
112 fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
113 (Vec::new(), SearchToken::default())
114 }
115 fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
116 fn update_matches(
117 &mut self,
118 matches: &[Self::Match],
119 active_match_index: Option<usize>,
120 token: SearchToken,
121 window: &mut Window,
122 cx: &mut Context<Self>,
123 );
124 fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
125 fn activate_match(
126 &mut self,
127 index: usize,
128 matches: &[Self::Match],
129 token: SearchToken,
130 window: &mut Window,
131 cx: &mut Context<Self>,
132 );
133 fn select_matches(
134 &mut self,
135 matches: &[Self::Match],
136 token: SearchToken,
137 window: &mut Window,
138 cx: &mut Context<Self>,
139 );
140 fn replace(
141 &mut self,
142 _: &Self::Match,
143 _: &SearchQuery,
144 _token: SearchToken,
145 _window: &mut Window,
146 _: &mut Context<Self>,
147 );
148 fn replace_all(
149 &mut self,
150 matches: &mut dyn Iterator<Item = &Self::Match>,
151 query: &SearchQuery,
152 token: SearchToken,
153 window: &mut Window,
154 cx: &mut Context<Self>,
155 ) {
156 for item in matches {
157 self.replace(item, query, token, window, cx);
158 }
159 }
160 fn match_index_for_direction(
161 &mut self,
162 matches: &[Self::Match],
163 current_index: usize,
164 direction: Direction,
165 count: usize,
166 _token: SearchToken,
167 _window: &mut Window,
168 _: &mut Context<Self>,
169 ) -> usize {
170 match direction {
171 Direction::Prev => {
172 let count = count % matches.len();
173 if current_index >= count {
174 current_index - count
175 } else {
176 matches.len() - (count - current_index)
177 }
178 }
179 Direction::Next => (current_index + count) % matches.len(),
180 }
181 }
182 fn find_matches(
183 &mut self,
184 query: Arc<SearchQuery>,
185 window: &mut Window,
186 cx: &mut Context<Self>,
187 ) -> Task<Vec<Self::Match>>;
188
189 fn find_matches_with_token(
190 &mut self,
191 query: Arc<SearchQuery>,
192 window: &mut Window,
193 cx: &mut Context<Self>,
194 ) -> Task<(Vec<Self::Match>, SearchToken)> {
195 let matches = self.find_matches(query, window, cx);
196 cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
197 }
198
199 fn active_match_index(
200 &mut self,
201 direction: Direction,
202 matches: &[Self::Match],
203 token: SearchToken,
204 window: &mut Window,
205 cx: &mut Context<Self>,
206 ) -> Option<usize>;
207 fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
208}
209
210pub trait SearchableItemHandle: ItemHandle {
211 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
212 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
213 fn supported_options(&self, cx: &App) -> SearchOptions;
214 fn subscribe_to_search_events(
215 &self,
216 window: &mut Window,
217 cx: &mut App,
218 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
219 ) -> Subscription;
220 fn clear_matches(&self, window: &mut Window, cx: &mut App);
221 fn update_matches(
222 &self,
223 matches: &AnyVec<dyn Send>,
224 active_match_index: Option<usize>,
225 token: SearchToken,
226 window: &mut Window,
227 cx: &mut App,
228 );
229 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
230 fn activate_match(
231 &self,
232 index: usize,
233 matches: &AnyVec<dyn Send>,
234 token: SearchToken,
235 window: &mut Window,
236 cx: &mut App,
237 );
238 fn select_matches(
239 &self,
240 matches: &AnyVec<dyn Send>,
241 token: SearchToken,
242 window: &mut Window,
243 cx: &mut App,
244 );
245 fn replace(
246 &self,
247 _: any_vec::element::ElementRef<'_, dyn Send>,
248 _: &SearchQuery,
249 token: SearchToken,
250 _window: &mut Window,
251 _: &mut App,
252 );
253 fn replace_all(
254 &self,
255 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
256 query: &SearchQuery,
257 token: SearchToken,
258 window: &mut Window,
259 cx: &mut App,
260 );
261 fn match_index_for_direction(
262 &self,
263 matches: &AnyVec<dyn Send>,
264 current_index: usize,
265 direction: Direction,
266 count: usize,
267 token: SearchToken,
268 window: &mut Window,
269 cx: &mut App,
270 ) -> usize;
271 fn find_matches(
272 &self,
273 query: Arc<SearchQuery>,
274 window: &mut Window,
275 cx: &mut App,
276 ) -> Task<AnyVec<dyn Send>>;
277 fn find_matches_with_token(
278 &self,
279 query: Arc<SearchQuery>,
280 window: &mut Window,
281 cx: &mut App,
282 ) -> Task<(AnyVec<dyn Send>, SearchToken)>;
283 fn active_match_index(
284 &self,
285 direction: Direction,
286 matches: &AnyVec<dyn Send>,
287 token: SearchToken,
288 window: &mut Window,
289 cx: &mut App,
290 ) -> Option<usize>;
291 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
292
293 fn toggle_filtered_search_ranges(
294 &mut self,
295 enabled: Option<FilteredSearchRange>,
296 window: &mut Window,
297 cx: &mut App,
298 );
299
300 fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
301}
302
303impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
304 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
305 Box::new(self.downgrade())
306 }
307
308 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
309 Box::new(self.clone())
310 }
311
312 fn supported_options(&self, cx: &App) -> SearchOptions {
313 self.read(cx).supported_options()
314 }
315
316 fn subscribe_to_search_events(
317 &self,
318 window: &mut Window,
319 cx: &mut App,
320 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
321 ) -> Subscription {
322 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
323 handler(event, window, cx)
324 })
325 }
326
327 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
328 self.update(cx, |this, cx| this.clear_matches(window, cx));
329 }
330 fn update_matches(
331 &self,
332 matches: &AnyVec<dyn Send>,
333 active_match_index: Option<usize>,
334 token: SearchToken,
335 window: &mut Window,
336 cx: &mut App,
337 ) {
338 let matches = matches.downcast_ref().unwrap();
339 self.update(cx, |this, cx| {
340 this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
341 });
342 }
343 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
344 self.update(cx, |this, cx| this.query_suggestion(window, cx))
345 }
346 fn activate_match(
347 &self,
348 index: usize,
349 matches: &AnyVec<dyn Send>,
350 token: SearchToken,
351 window: &mut Window,
352 cx: &mut App,
353 ) {
354 let matches = matches.downcast_ref().unwrap();
355 self.update(cx, |this, cx| {
356 this.activate_match(index, matches.as_slice(), token, window, cx)
357 });
358 }
359
360 fn select_matches(
361 &self,
362 matches: &AnyVec<dyn Send>,
363 token: SearchToken,
364 window: &mut Window,
365 cx: &mut App,
366 ) {
367 let matches = matches.downcast_ref().unwrap();
368 self.update(cx, |this, cx| {
369 this.select_matches(matches.as_slice(), token, window, cx)
370 });
371 }
372
373 fn match_index_for_direction(
374 &self,
375 matches: &AnyVec<dyn Send>,
376 current_index: usize,
377 direction: Direction,
378 count: usize,
379 token: SearchToken,
380 window: &mut Window,
381 cx: &mut App,
382 ) -> usize {
383 let matches = matches.downcast_ref().unwrap();
384 self.update(cx, |this, cx| {
385 this.match_index_for_direction(
386 matches.as_slice(),
387 current_index,
388 direction,
389 count,
390 token,
391 window,
392 cx,
393 )
394 })
395 }
396 fn find_matches(
397 &self,
398 query: Arc<SearchQuery>,
399 window: &mut Window,
400 cx: &mut App,
401 ) -> Task<AnyVec<dyn Send>> {
402 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
403 window.spawn(cx, async |_| {
404 let matches = matches.await;
405 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
406 {
407 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
408 for mat in matches {
409 any_matches.push(mat);
410 }
411 }
412 any_matches
413 })
414 }
415 fn find_matches_with_token(
416 &self,
417 query: Arc<SearchQuery>,
418 window: &mut Window,
419 cx: &mut App,
420 ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
421 let matches_with_token = self.update(cx, |this, cx| {
422 this.find_matches_with_token(query, window, cx)
423 });
424 window.spawn(cx, async |_| {
425 let (matches, token) = matches_with_token.await;
426 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
427 {
428 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
429 for mat in matches {
430 any_matches.push(mat);
431 }
432 }
433 (any_matches, token)
434 })
435 }
436 fn active_match_index(
437 &self,
438 direction: Direction,
439 matches: &AnyVec<dyn Send>,
440 token: SearchToken,
441 window: &mut Window,
442 cx: &mut App,
443 ) -> Option<usize> {
444 let matches = matches.downcast_ref()?;
445 self.update(cx, |this, cx| {
446 this.active_match_index(direction, matches.as_slice(), token, window, cx)
447 })
448 }
449
450 fn replace(
451 &self,
452 mat: any_vec::element::ElementRef<'_, dyn Send>,
453 query: &SearchQuery,
454 token: SearchToken,
455 window: &mut Window,
456 cx: &mut App,
457 ) {
458 let mat = mat.downcast_ref().unwrap();
459 self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
460 }
461
462 fn replace_all(
463 &self,
464 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
465 query: &SearchQuery,
466 token: SearchToken,
467 window: &mut Window,
468 cx: &mut App,
469 ) {
470 self.update(cx, |this, cx| {
471 this.replace_all(
472 &mut matches.map(|m| m.downcast_ref().unwrap()),
473 query,
474 token,
475 window,
476 cx,
477 );
478 })
479 }
480
481 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
482 self.update(cx, |this, cx| {
483 this.search_bar_visibility_changed(visible, window, cx)
484 });
485 }
486
487 fn toggle_filtered_search_ranges(
488 &mut self,
489 enabled: Option<FilteredSearchRange>,
490 window: &mut Window,
491 cx: &mut App,
492 ) {
493 self.update(cx, |this, cx| {
494 this.toggle_filtered_search_ranges(enabled, window, cx)
495 });
496 }
497 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
498 self.update(cx, |this, cx| {
499 this.set_search_is_case_sensitive(enabled, cx)
500 });
501 }
502}
503
504impl From<Box<dyn SearchableItemHandle>> for AnyView {
505 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
506 this.to_any_view()
507 }
508}
509
510impl From<&Box<dyn SearchableItemHandle>> for AnyView {
511 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
512 this.to_any_view()
513 }
514}
515
516impl PartialEq for Box<dyn SearchableItemHandle> {
517 fn eq(&self, other: &Self) -> bool {
518 self.item_id() == other.item_id()
519 }
520}
521
522impl Eq for Box<dyn SearchableItemHandle> {}
523
524pub trait WeakSearchableItemHandle: WeakItemHandle {
525 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
526
527 fn into_any(self) -> AnyWeakEntity;
528}
529
530impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
531 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
532 Some(Box::new(self.upgrade()?))
533 }
534
535 fn into_any(self) -> AnyWeakEntity {
536 self.into()
537 }
538}
539
540impl PartialEq for Box<dyn WeakSearchableItemHandle> {
541 fn eq(&self, other: &Self) -> bool {
542 self.id() == other.id()
543 }
544}
545
546impl Eq for Box<dyn WeakSearchableItemHandle> {}
547
548impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
549 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
550 self.id().hash(state)
551 }
552}