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