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(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
120 fn activate_match(
121 &mut self,
122 index: usize,
123 matches: &[Self::Match],
124 token: SearchToken,
125 window: &mut Window,
126 cx: &mut Context<Self>,
127 );
128 fn select_matches(
129 &mut self,
130 matches: &[Self::Match],
131 token: SearchToken,
132 window: &mut Window,
133 cx: &mut Context<Self>,
134 );
135 fn replace(
136 &mut self,
137 _: &Self::Match,
138 _: &SearchQuery,
139 _token: SearchToken,
140 _window: &mut Window,
141 _: &mut Context<Self>,
142 );
143 fn replace_all(
144 &mut self,
145 matches: &mut dyn Iterator<Item = &Self::Match>,
146 query: &SearchQuery,
147 token: SearchToken,
148 window: &mut Window,
149 cx: &mut Context<Self>,
150 ) {
151 for item in matches {
152 self.replace(item, query, token, window, cx);
153 }
154 }
155 fn match_index_for_direction(
156 &mut self,
157 matches: &[Self::Match],
158 current_index: usize,
159 direction: Direction,
160 count: usize,
161 _token: SearchToken,
162 _window: &mut Window,
163 _: &mut Context<Self>,
164 ) -> usize {
165 match direction {
166 Direction::Prev => {
167 let count = count % matches.len();
168 if current_index >= count {
169 current_index - count
170 } else {
171 matches.len() - (count - current_index)
172 }
173 }
174 Direction::Next => (current_index + count) % matches.len(),
175 }
176 }
177 fn find_matches(
178 &mut self,
179 query: Arc<SearchQuery>,
180 window: &mut Window,
181 cx: &mut Context<Self>,
182 ) -> Task<Vec<Self::Match>>;
183
184 fn find_matches_with_token(
185 &mut self,
186 query: Arc<SearchQuery>,
187 window: &mut Window,
188 cx: &mut Context<Self>,
189 ) -> Task<(Vec<Self::Match>, SearchToken)> {
190 let matches = self.find_matches(query, window, cx);
191 cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
192 }
193
194 fn active_match_index(
195 &mut self,
196 direction: Direction,
197 matches: &[Self::Match],
198 token: SearchToken,
199 window: &mut Window,
200 cx: &mut Context<Self>,
201 ) -> Option<usize>;
202 fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
203}
204
205pub trait SearchableItemHandle: ItemHandle {
206 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
207 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
208 fn supported_options(&self, cx: &App) -> SearchOptions;
209 fn subscribe_to_search_events(
210 &self,
211 window: &mut Window,
212 cx: &mut App,
213 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
214 ) -> Subscription;
215 fn clear_matches(&self, window: &mut Window, cx: &mut App);
216 fn update_matches(
217 &self,
218 matches: &AnyVec<dyn Send>,
219 active_match_index: Option<usize>,
220 token: SearchToken,
221 window: &mut Window,
222 cx: &mut App,
223 );
224 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
225 fn activate_match(
226 &self,
227 index: usize,
228 matches: &AnyVec<dyn Send>,
229 token: SearchToken,
230 window: &mut Window,
231 cx: &mut App,
232 );
233 fn select_matches(
234 &self,
235 matches: &AnyVec<dyn Send>,
236 token: SearchToken,
237 window: &mut Window,
238 cx: &mut App,
239 );
240 fn replace(
241 &self,
242 _: any_vec::element::ElementRef<'_, dyn Send>,
243 _: &SearchQuery,
244 token: SearchToken,
245 _window: &mut Window,
246 _: &mut App,
247 );
248 fn replace_all(
249 &self,
250 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
251 query: &SearchQuery,
252 token: SearchToken,
253 window: &mut Window,
254 cx: &mut App,
255 );
256 fn match_index_for_direction(
257 &self,
258 matches: &AnyVec<dyn Send>,
259 current_index: usize,
260 direction: Direction,
261 count: usize,
262 token: SearchToken,
263 window: &mut Window,
264 cx: &mut App,
265 ) -> usize;
266 fn find_matches(
267 &self,
268 query: Arc<SearchQuery>,
269 window: &mut Window,
270 cx: &mut App,
271 ) -> Task<AnyVec<dyn Send>>;
272 fn find_matches_with_token(
273 &self,
274 query: Arc<SearchQuery>,
275 window: &mut Window,
276 cx: &mut App,
277 ) -> Task<(AnyVec<dyn Send>, SearchToken)>;
278 fn active_match_index(
279 &self,
280 direction: Direction,
281 matches: &AnyVec<dyn Send>,
282 token: SearchToken,
283 window: &mut Window,
284 cx: &mut App,
285 ) -> Option<usize>;
286 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
287
288 fn toggle_filtered_search_ranges(
289 &mut self,
290 enabled: Option<FilteredSearchRange>,
291 window: &mut Window,
292 cx: &mut App,
293 );
294
295 fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
296}
297
298impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
299 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
300 Box::new(self.downgrade())
301 }
302
303 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
304 Box::new(self.clone())
305 }
306
307 fn supported_options(&self, cx: &App) -> SearchOptions {
308 self.read(cx).supported_options()
309 }
310
311 fn subscribe_to_search_events(
312 &self,
313 window: &mut Window,
314 cx: &mut App,
315 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
316 ) -> Subscription {
317 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
318 handler(event, window, cx)
319 })
320 }
321
322 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
323 self.update(cx, |this, cx| this.clear_matches(window, cx));
324 }
325 fn update_matches(
326 &self,
327 matches: &AnyVec<dyn Send>,
328 active_match_index: Option<usize>,
329 token: SearchToken,
330 window: &mut Window,
331 cx: &mut App,
332 ) {
333 let matches = matches.downcast_ref().unwrap();
334 self.update(cx, |this, cx| {
335 this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
336 });
337 }
338 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
339 self.update(cx, |this, cx| this.query_suggestion(window, cx))
340 }
341 fn activate_match(
342 &self,
343 index: usize,
344 matches: &AnyVec<dyn Send>,
345 token: SearchToken,
346 window: &mut Window,
347 cx: &mut App,
348 ) {
349 let matches = matches.downcast_ref().unwrap();
350 self.update(cx, |this, cx| {
351 this.activate_match(index, matches.as_slice(), token, window, cx)
352 });
353 }
354
355 fn select_matches(
356 &self,
357 matches: &AnyVec<dyn Send>,
358 token: SearchToken,
359 window: &mut Window,
360 cx: &mut App,
361 ) {
362 let matches = matches.downcast_ref().unwrap();
363 self.update(cx, |this, cx| {
364 this.select_matches(matches.as_slice(), token, window, cx)
365 });
366 }
367
368 fn match_index_for_direction(
369 &self,
370 matches: &AnyVec<dyn Send>,
371 current_index: usize,
372 direction: Direction,
373 count: usize,
374 token: SearchToken,
375 window: &mut Window,
376 cx: &mut App,
377 ) -> usize {
378 let matches = matches.downcast_ref().unwrap();
379 self.update(cx, |this, cx| {
380 this.match_index_for_direction(
381 matches.as_slice(),
382 current_index,
383 direction,
384 count,
385 token,
386 window,
387 cx,
388 )
389 })
390 }
391 fn find_matches(
392 &self,
393 query: Arc<SearchQuery>,
394 window: &mut Window,
395 cx: &mut App,
396 ) -> Task<AnyVec<dyn Send>> {
397 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
398 window.spawn(cx, async |_| {
399 let matches = matches.await;
400 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
401 {
402 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
403 for mat in matches {
404 any_matches.push(mat);
405 }
406 }
407 any_matches
408 })
409 }
410 fn find_matches_with_token(
411 &self,
412 query: Arc<SearchQuery>,
413 window: &mut Window,
414 cx: &mut App,
415 ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
416 let matches_with_token = self.update(cx, |this, cx| {
417 this.find_matches_with_token(query, window, cx)
418 });
419 window.spawn(cx, async |_| {
420 let (matches, token) = matches_with_token.await;
421 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
422 {
423 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
424 for mat in matches {
425 any_matches.push(mat);
426 }
427 }
428 (any_matches, token)
429 })
430 }
431 fn active_match_index(
432 &self,
433 direction: Direction,
434 matches: &AnyVec<dyn Send>,
435 token: SearchToken,
436 window: &mut Window,
437 cx: &mut App,
438 ) -> Option<usize> {
439 let matches = matches.downcast_ref()?;
440 self.update(cx, |this, cx| {
441 this.active_match_index(direction, matches.as_slice(), token, window, cx)
442 })
443 }
444
445 fn replace(
446 &self,
447 mat: any_vec::element::ElementRef<'_, dyn Send>,
448 query: &SearchQuery,
449 token: SearchToken,
450 window: &mut Window,
451 cx: &mut App,
452 ) {
453 let mat = mat.downcast_ref().unwrap();
454 self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
455 }
456
457 fn replace_all(
458 &self,
459 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
460 query: &SearchQuery,
461 token: SearchToken,
462 window: &mut Window,
463 cx: &mut App,
464 ) {
465 self.update(cx, |this, cx| {
466 this.replace_all(
467 &mut matches.map(|m| m.downcast_ref().unwrap()),
468 query,
469 token,
470 window,
471 cx,
472 );
473 })
474 }
475
476 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
477 self.update(cx, |this, cx| {
478 this.search_bar_visibility_changed(visible, window, cx)
479 });
480 }
481
482 fn toggle_filtered_search_ranges(
483 &mut self,
484 enabled: Option<FilteredSearchRange>,
485 window: &mut Window,
486 cx: &mut App,
487 ) {
488 self.update(cx, |this, cx| {
489 this.toggle_filtered_search_ranges(enabled, window, cx)
490 });
491 }
492 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
493 self.update(cx, |this, cx| {
494 this.set_search_is_case_sensitive(enabled, cx)
495 });
496 }
497}
498
499impl From<Box<dyn SearchableItemHandle>> for AnyView {
500 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
501 this.to_any_view()
502 }
503}
504
505impl From<&Box<dyn SearchableItemHandle>> for AnyView {
506 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
507 this.to_any_view()
508 }
509}
510
511impl PartialEq for Box<dyn SearchableItemHandle> {
512 fn eq(&self, other: &Self) -> bool {
513 self.item_id() == other.item_id()
514 }
515}
516
517impl Eq for Box<dyn SearchableItemHandle> {}
518
519pub trait WeakSearchableItemHandle: WeakItemHandle {
520 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
521
522 fn into_any(self) -> AnyWeakEntity;
523}
524
525impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
526 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
527 Some(Box::new(self.upgrade()?))
528 }
529
530 fn into_any(self) -> AnyWeakEntity {
531 self.into()
532 }
533}
534
535impl PartialEq for Box<dyn WeakSearchableItemHandle> {
536 fn eq(&self, other: &Self) -> bool {
537 self.id() == other.id()
538 }
539}
540
541impl Eq for Box<dyn WeakSearchableItemHandle> {}
542
543impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
544 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
545 self.id().hash(state)
546 }
547}