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