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