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