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