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(Debug, Clone)]
29pub enum SearchEvent {
30 MatchesInvalidated,
31 ActiveMatchChanged,
32}
33
34#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
35pub enum Direction {
36 Prev,
37 #[default]
38 Next,
39}
40
41impl Direction {
42 pub fn opposite(&self) -> Self {
43 match self {
44 Direction::Prev => Direction::Next,
45 Direction::Next => Direction::Prev,
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, Default)]
51pub struct SearchOptions {
52 pub case: bool,
53 pub word: bool,
54 pub regex: bool,
55 /// Specifies whether the supports search & replace.
56 pub replacement: bool,
57 pub selection: bool,
58 pub select_all: bool,
59 pub find_in_results: bool,
60}
61
62// Whether to always select the current selection (even if empty)
63// or to use the default (restoring the previous search ranges if some,
64// otherwise using the whole file).
65#[derive(Clone, Copy, Debug, Default, PartialEq)]
66pub enum FilteredSearchRange {
67 Selection,
68 #[default]
69 Default,
70}
71
72pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
73 type Match: Any + Sync + Send + Clone;
74
75 fn supported_options(&self) -> SearchOptions {
76 SearchOptions {
77 case: true,
78 word: true,
79 regex: true,
80 replacement: true,
81 selection: true,
82 select_all: true,
83 find_in_results: false,
84 }
85 }
86
87 fn search_bar_visibility_changed(
88 &mut self,
89 _visible: bool,
90 _window: &mut Window,
91 _cx: &mut Context<Self>,
92 ) {
93 }
94
95 fn has_filtered_search_ranges(&mut self) -> bool {
96 self.supported_options().selection
97 }
98
99 fn toggle_filtered_search_ranges(
100 &mut self,
101 _enabled: Option<FilteredSearchRange>,
102 _window: &mut Window,
103 _cx: &mut Context<Self>,
104 ) {
105 }
106
107 fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
108 (Vec::new(), SearchToken::default())
109 }
110 fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
111 fn update_matches(
112 &mut self,
113 matches: &[Self::Match],
114 active_match_index: Option<usize>,
115 token: SearchToken,
116 window: &mut Window,
117 cx: &mut Context<Self>,
118 );
119 fn query_suggestion(
120 &mut self,
121 ignore_settings: bool,
122 window: &mut Window,
123 cx: &mut Context<Self>,
124 ) -> 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, ignore_settings: bool, 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, ignore_settings: bool, window: &mut Window, cx: &mut App) -> String {
344 self.update(cx, |this, cx| {
345 this.query_suggestion(ignore_settings, window, cx)
346 })
347 }
348 fn activate_match(
349 &self,
350 index: usize,
351 matches: &AnyVec<dyn Send>,
352 token: SearchToken,
353 window: &mut Window,
354 cx: &mut App,
355 ) {
356 let matches = matches.downcast_ref().unwrap();
357 self.update(cx, |this, cx| {
358 this.activate_match(index, matches.as_slice(), token, window, cx)
359 });
360 }
361
362 fn select_matches(
363 &self,
364 matches: &AnyVec<dyn Send>,
365 token: SearchToken,
366 window: &mut Window,
367 cx: &mut App,
368 ) {
369 let matches = matches.downcast_ref().unwrap();
370 self.update(cx, |this, cx| {
371 this.select_matches(matches.as_slice(), token, window, cx)
372 });
373 }
374
375 fn match_index_for_direction(
376 &self,
377 matches: &AnyVec<dyn Send>,
378 current_index: usize,
379 direction: Direction,
380 count: usize,
381 token: SearchToken,
382 window: &mut Window,
383 cx: &mut App,
384 ) -> usize {
385 let matches = matches.downcast_ref().unwrap();
386 self.update(cx, |this, cx| {
387 this.match_index_for_direction(
388 matches.as_slice(),
389 current_index,
390 direction,
391 count,
392 token,
393 window,
394 cx,
395 )
396 })
397 }
398 fn find_matches(
399 &self,
400 query: Arc<SearchQuery>,
401 window: &mut Window,
402 cx: &mut App,
403 ) -> Task<AnyVec<dyn Send>> {
404 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
405 window.spawn(cx, async |_| {
406 let matches = matches.await;
407 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
408 {
409 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
410 for mat in matches {
411 any_matches.push(mat);
412 }
413 }
414 any_matches
415 })
416 }
417 fn find_matches_with_token(
418 &self,
419 query: Arc<SearchQuery>,
420 window: &mut Window,
421 cx: &mut App,
422 ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
423 let matches_with_token = self.update(cx, |this, cx| {
424 this.find_matches_with_token(query, window, cx)
425 });
426 window.spawn(cx, async |_| {
427 let (matches, token) = matches_with_token.await;
428 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
429 {
430 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
431 for mat in matches {
432 any_matches.push(mat);
433 }
434 }
435 (any_matches, token)
436 })
437 }
438 fn active_match_index(
439 &self,
440 direction: Direction,
441 matches: &AnyVec<dyn Send>,
442 token: SearchToken,
443 window: &mut Window,
444 cx: &mut App,
445 ) -> Option<usize> {
446 let matches = matches.downcast_ref()?;
447 self.update(cx, |this, cx| {
448 this.active_match_index(direction, matches.as_slice(), token, window, cx)
449 })
450 }
451
452 fn replace(
453 &self,
454 mat: any_vec::element::ElementRef<'_, dyn Send>,
455 query: &SearchQuery,
456 token: SearchToken,
457 window: &mut Window,
458 cx: &mut App,
459 ) {
460 let mat = mat.downcast_ref().unwrap();
461 self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
462 }
463
464 fn replace_all(
465 &self,
466 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
467 query: &SearchQuery,
468 token: SearchToken,
469 window: &mut Window,
470 cx: &mut App,
471 ) {
472 self.update(cx, |this, cx| {
473 this.replace_all(
474 &mut matches.map(|m| m.downcast_ref().unwrap()),
475 query,
476 token,
477 window,
478 cx,
479 );
480 })
481 }
482
483 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
484 self.update(cx, |this, cx| {
485 this.search_bar_visibility_changed(visible, window, cx)
486 });
487 }
488
489 fn toggle_filtered_search_ranges(
490 &mut self,
491 enabled: Option<FilteredSearchRange>,
492 window: &mut Window,
493 cx: &mut App,
494 ) {
495 self.update(cx, |this, cx| {
496 this.toggle_filtered_search_ranges(enabled, window, cx)
497 });
498 }
499 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
500 self.update(cx, |this, cx| {
501 this.set_search_is_case_sensitive(enabled, cx)
502 });
503 }
504}
505
506impl From<Box<dyn SearchableItemHandle>> for AnyView {
507 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
508 this.to_any_view()
509 }
510}
511
512impl From<&Box<dyn SearchableItemHandle>> for AnyView {
513 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
514 this.to_any_view()
515 }
516}
517
518impl PartialEq for Box<dyn SearchableItemHandle> {
519 fn eq(&self, other: &Self) -> bool {
520 self.item_id() == other.item_id()
521 }
522}
523
524impl Eq for Box<dyn SearchableItemHandle> {}
525
526pub trait WeakSearchableItemHandle: WeakItemHandle {
527 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
528
529 fn into_any(self) -> AnyWeakEntity;
530}
531
532impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
533 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
534 Some(Box::new(self.upgrade()?))
535 }
536
537 fn into_any(self) -> AnyWeakEntity {
538 self.into()
539 }
540}
541
542impl PartialEq for Box<dyn WeakSearchableItemHandle> {
543 fn eq(&self, other: &Self) -> bool {
544 self.id() == other.id()
545 }
546}
547
548impl Eq for Box<dyn WeakSearchableItemHandle> {}
549
550impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
551 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
552 self.id().hash(state)
553 }
554}