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 fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
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 window: &mut Window,
189 cx: &mut App,
190 );
191 fn select_matches(&self, matches: &AnyVec<dyn Send>, window: &mut Window, cx: &mut App);
192 fn replace(
193 &self,
194 _: any_vec::element::ElementRef<'_, dyn Send>,
195 _: &SearchQuery,
196 _window: &mut Window,
197 _: &mut App,
198 );
199 fn replace_all(
200 &self,
201 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
202 query: &SearchQuery,
203 window: &mut Window,
204 cx: &mut App,
205 );
206 fn match_index_for_direction(
207 &self,
208 matches: &AnyVec<dyn Send>,
209 current_index: usize,
210 direction: Direction,
211 count: usize,
212 window: &mut Window,
213 cx: &mut App,
214 ) -> usize;
215 fn find_matches(
216 &self,
217 query: Arc<SearchQuery>,
218 window: &mut Window,
219 cx: &mut App,
220 ) -> Task<AnyVec<dyn Send>>;
221 fn active_match_index(
222 &self,
223 direction: Direction,
224 matches: &AnyVec<dyn Send>,
225 window: &mut Window,
226 cx: &mut App,
227 ) -> Option<usize>;
228 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
229
230 fn toggle_filtered_search_ranges(
231 &mut self,
232 enabled: Option<FilteredSearchRange>,
233 window: &mut Window,
234 cx: &mut App,
235 );
236
237 fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
238}
239
240impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
241 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
242 Box::new(self.downgrade())
243 }
244
245 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
246 Box::new(self.clone())
247 }
248
249 fn supported_options(&self, cx: &App) -> SearchOptions {
250 self.read(cx).supported_options()
251 }
252
253 fn subscribe_to_search_events(
254 &self,
255 window: &mut Window,
256 cx: &mut App,
257 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
258 ) -> Subscription {
259 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
260 handler(event, window, cx)
261 })
262 }
263
264 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
265 self.update(cx, |this, cx| this.clear_matches(window, cx));
266 }
267 fn update_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.update_matches(matches.as_slice(), window, cx)
271 });
272 }
273 fn query_suggestion(&self, window: &mut Window, cx: &mut App) -> String {
274 self.update(cx, |this, cx| this.query_suggestion(window, cx))
275 }
276 fn activate_match(
277 &self,
278 index: usize,
279 matches: &AnyVec<dyn Send>,
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(), 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 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
394 self.update(cx, |this, cx| {
395 this.set_search_is_case_sensitive(enabled, cx)
396 });
397 }
398}
399
400impl From<Box<dyn SearchableItemHandle>> for AnyView {
401 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
402 this.to_any_view()
403 }
404}
405
406impl From<&Box<dyn SearchableItemHandle>> for AnyView {
407 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
408 this.to_any_view()
409 }
410}
411
412impl PartialEq for Box<dyn SearchableItemHandle> {
413 fn eq(&self, other: &Self) -> bool {
414 self.item_id() == other.item_id()
415 }
416}
417
418impl Eq for Box<dyn SearchableItemHandle> {}
419
420pub trait WeakSearchableItemHandle: WeakItemHandle {
421 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
422
423 fn into_any(self) -> AnyWeakEntity;
424}
425
426impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
427 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
428 Some(Box::new(self.upgrade()?))
429 }
430
431 fn into_any(self) -> AnyWeakEntity {
432 self.into()
433 }
434}
435
436impl PartialEq for Box<dyn WeakSearchableItemHandle> {
437 fn eq(&self, other: &Self) -> bool {
438 self.id() == other.id()
439 }
440}
441
442impl Eq for Box<dyn WeakSearchableItemHandle> {}
443
444impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
445 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
446 self.id().hash(state)
447 }
448}