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