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 window: &mut Window,
100 cx: &mut Context<Self>,
101 );
102 fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String;
103 fn activate_match(
104 &mut self,
105 index: usize,
106 matches: &[Self::Match],
107 collapse: bool,
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(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
184 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String;
185 fn activate_match(
186 &self,
187 index: usize,
188 matches: &AnyVec<dyn Send>,
189 collapse: bool,
190 window: &mut Window,
191 cx: &mut App,
192 );
193 fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
194 fn replace(
195 &self,
196 _: any_vec::element::ElementRef<'_, dyn Send>,
197 _: &SearchQuery,
198 _window: &mut Window,
199 _: &mut App,
200 );
201 fn replace_all(
202 &self,
203 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
204 query: &SearchQuery,
205 window: &mut Window,
206 cx: &mut App,
207 );
208 fn match_index_for_direction(
209 &self,
210 matches: &AnyVec<dyn Send>,
211 current_index: usize,
212 direction: Direction,
213 count: usize,
214 window: &mut Window,
215 cx: &mut App,
216 ) -> usize;
217 fn find_matches(
218 &self,
219 query: Arc<SearchQuery>,
220 window: &mut Window,
221 cx: &mut App,
222 ) -> Task<AnyVec<dyn Send>>;
223 fn active_match_index(
224 &self,
225 direction: Direction,
226 matches: &AnyVec<dyn Send>,
227 window: &mut Window,
228 cx: &mut App,
229 ) -> Option<usize>;
230 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
231
232 fn toggle_filtered_search_ranges(
233 &mut self,
234 enabled: Option<FilteredSearchRange>,
235 window: &mut Window,
236 cx: &mut App,
237 );
238
239 fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
240}
241
242impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
243 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
244 Box::new(self.downgrade())
245 }
246
247 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
248 Box::new(self.clone())
249 }
250
251 fn supported_options(&self, cx: &App) -> SearchOptions {
252 self.read(cx).supported_options()
253 }
254
255 fn subscribe_to_search_events(
256 &self,
257 window: &mut Window,
258 cx: &mut App,
259 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
260 ) -> Subscription {
261 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
262 handler(event, window, cx)
263 })
264 }
265
266 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
267 self.update(cx, |this, cx| this.clear_matches(window, cx));
268 }
269 fn update_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
270 let matches = matches.downcast_ref().unwrap();
271 self.update(cx, |this, cx| {
272 this.update_matches(matches.as_slice(), window, cx)
273 });
274 }
275 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
276 self.update(cx, |this, cx| this.query_suggestion(window, cx))
277 }
278 fn activate_match(
279 &self,
280 index: usize,
281 matches: &AnyVec<dyn Send>,
282 collapse: bool,
283 window: &mut Window,
284 cx: &mut App,
285 ) {
286 let matches = matches.downcast_ref().unwrap();
287 self.update(cx, |this, cx| {
288 this.activate_match(index, matches.as_slice(), collapse, window, cx)
289 });
290 }
291
292 fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App) {
293 let matches = matches.downcast_ref().unwrap();
294 self.update(cx, |this, cx| {
295 this.select_matches(matches.as_slice(), window, cx)
296 });
297 }
298
299 fn match_index_for_direction(
300 &self,
301 matches: &AnyVec<dyn Send>,
302 current_index: usize,
303 direction: Direction,
304 count: usize,
305 window: &mut Window,
306 cx: &mut App,
307 ) -> usize {
308 let matches = matches.downcast_ref().unwrap();
309 self.update(cx, |this, cx| {
310 this.match_index_for_direction(
311 matches.as_slice(),
312 current_index,
313 direction,
314 count,
315 window,
316 cx,
317 )
318 })
319 }
320 fn find_matches(
321 &self,
322 query: Arc<SearchQuery>,
323 window: &mut Window,
324 cx: &mut App,
325 ) -> Task<AnyVec<dyn Send>> {
326 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
327 window.spawn(cx, async |_| {
328 let matches = matches.await;
329 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
330 {
331 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
332 for mat in matches {
333 any_matches.push(mat);
334 }
335 }
336 any_matches
337 })
338 }
339 fn active_match_index(
340 &self,
341 direction: Direction,
342 matches: &AnyVec<dyn Send>,
343 window: &mut Window,
344 cx: &mut App,
345 ) -> Option<usize> {
346 let matches = matches.downcast_ref()?;
347 self.update(cx, |this, cx| {
348 this.active_match_index(direction, matches.as_slice(), window, cx)
349 })
350 }
351
352 fn replace(
353 &self,
354 mat: any_vec::element::ElementRef<'_, dyn Send>,
355 query: &SearchQuery,
356 window: &mut Window,
357 cx: &mut App,
358 ) {
359 let mat = mat.downcast_ref().unwrap();
360 self.update(cx, |this, cx| this.replace(mat, query, window, cx))
361 }
362
363 fn replace_all(
364 &self,
365 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
366 query: &SearchQuery,
367 window: &mut Window,
368 cx: &mut App,
369 ) {
370 self.update(cx, |this, cx| {
371 this.replace_all(
372 &mut matches.map(|m| m.downcast_ref().unwrap()),
373 query,
374 window,
375 cx,
376 );
377 })
378 }
379
380 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
381 self.update(cx, |this, cx| {
382 this.search_bar_visibility_changed(visible, window, cx)
383 });
384 }
385
386 fn toggle_filtered_search_ranges(
387 &mut self,
388 enabled: Option<FilteredSearchRange>,
389 window: &mut Window,
390 cx: &mut App,
391 ) {
392 self.update(cx, |this, cx| {
393 this.toggle_filtered_search_ranges(enabled, window, cx)
394 });
395 }
396 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
397 self.update(cx, |this, cx| {
398 this.set_search_is_case_sensitive(enabled, cx)
399 });
400 }
401}
402
403impl From<Box<dyn SearchableItemHandle>> for AnyView {
404 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
405 this.to_any()
406 }
407}
408
409impl From<&Box<dyn SearchableItemHandle>> for AnyView {
410 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
411 this.to_any()
412 }
413}
414
415impl PartialEq for Box<dyn SearchableItemHandle> {
416 fn eq(&self, other: &Self) -> bool {
417 self.item_id() == other.item_id()
418 }
419}
420
421impl Eq for Box<dyn SearchableItemHandle> {}
422
423pub trait WeakSearchableItemHandle: WeakItemHandle {
424 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
425
426 fn into_any(self) -> AnyWeakEntity;
427}
428
429impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
430 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
431 Some(Box::new(self.upgrade()?))
432 }
433
434 fn into_any(self) -> AnyWeakEntity {
435 self.into()
436 }
437}
438
439impl PartialEq for Box<dyn WeakSearchableItemHandle> {
440 fn eq(&self, other: &Self) -> bool {
441 self.id() == other.id()
442 }
443}
444
445impl Eq for Box<dyn WeakSearchableItemHandle> {}
446
447impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
448 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
449 self.id().hash(state)
450 }
451}