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