1use std::{any::Any, sync::Arc};
2
3use any_vec::AnyVec;
4use gpui::{
5 AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
6 WeakView, WindowContext,
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 item supports search & replace.
43 pub replacement: bool,
44}
45
46pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
47 type Match: Any + Sync + Send + Clone;
48
49 fn supported_options() -> SearchOptions {
50 SearchOptions {
51 case: true,
52 word: true,
53 regex: true,
54 replacement: true,
55 }
56 }
57
58 fn search_bar_visibility_changed(&mut self, _visible: bool, _cx: &mut ViewContext<Self>) {}
59
60 fn has_filtered_search_ranges(&mut self) -> bool {
61 false
62 }
63
64 fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
65 fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
66 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
67 fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
68 fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
69 fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
70 fn match_index_for_direction(
71 &mut self,
72 matches: &[Self::Match],
73 current_index: usize,
74 direction: Direction,
75 count: usize,
76 _: &mut ViewContext<Self>,
77 ) -> usize {
78 match direction {
79 Direction::Prev => {
80 let count = count % matches.len();
81 if current_index >= count {
82 current_index - count
83 } else {
84 matches.len() - (count - current_index)
85 }
86 }
87 Direction::Next => (current_index + count) % matches.len(),
88 }
89 }
90 fn find_matches(
91 &mut self,
92 query: Arc<SearchQuery>,
93 cx: &mut ViewContext<Self>,
94 ) -> Task<Vec<Self::Match>>;
95 fn active_match_index(
96 &mut self,
97 matches: &[Self::Match],
98 cx: &mut ViewContext<Self>,
99 ) -> Option<usize>;
100}
101
102pub trait SearchableItemHandle: ItemHandle {
103 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
104 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
105 fn supported_options(&self) -> SearchOptions;
106 fn subscribe_to_search_events(
107 &self,
108 cx: &mut WindowContext,
109 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
110 ) -> Subscription;
111 fn clear_matches(&self, cx: &mut WindowContext);
112 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
113 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
114 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
115 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
116 fn replace(
117 &self,
118 _: any_vec::element::ElementRef<'_, dyn Send>,
119 _: &SearchQuery,
120 _: &mut WindowContext,
121 );
122 fn match_index_for_direction(
123 &self,
124 matches: &AnyVec<dyn Send>,
125 current_index: usize,
126 direction: Direction,
127 count: usize,
128 cx: &mut WindowContext,
129 ) -> usize;
130 fn find_matches(
131 &self,
132 query: Arc<SearchQuery>,
133 cx: &mut WindowContext,
134 ) -> Task<AnyVec<dyn Send>>;
135 fn active_match_index(
136 &self,
137 matches: &AnyVec<dyn Send>,
138 cx: &mut WindowContext,
139 ) -> Option<usize>;
140 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext);
141}
142
143impl<T: SearchableItem> SearchableItemHandle for View<T> {
144 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
145 Box::new(self.downgrade())
146 }
147
148 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
149 Box::new(self.clone())
150 }
151
152 fn supported_options(&self) -> SearchOptions {
153 T::supported_options()
154 }
155
156 fn subscribe_to_search_events(
157 &self,
158 cx: &mut WindowContext,
159 handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
160 ) -> Subscription {
161 cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
162 }
163
164 fn clear_matches(&self, cx: &mut WindowContext) {
165 self.update(cx, |this, cx| this.clear_matches(cx));
166 }
167 fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
168 let matches = matches.downcast_ref().unwrap();
169 self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
170 }
171 fn query_suggestion(&self, cx: &mut WindowContext) -> String {
172 self.update(cx, |this, cx| this.query_suggestion(cx))
173 }
174 fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
175 let matches = matches.downcast_ref().unwrap();
176 self.update(cx, |this, cx| {
177 this.activate_match(index, matches.as_slice(), cx)
178 });
179 }
180
181 fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
182 let matches = matches.downcast_ref().unwrap();
183 self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
184 }
185
186 fn match_index_for_direction(
187 &self,
188 matches: &AnyVec<dyn Send>,
189 current_index: usize,
190 direction: Direction,
191 count: usize,
192 cx: &mut WindowContext,
193 ) -> usize {
194 let matches = matches.downcast_ref().unwrap();
195 self.update(cx, |this, cx| {
196 this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
197 })
198 }
199 fn find_matches(
200 &self,
201 query: Arc<SearchQuery>,
202 cx: &mut WindowContext,
203 ) -> Task<AnyVec<dyn Send>> {
204 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
205 cx.spawn(|_| async {
206 let matches = matches.await;
207 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
208 {
209 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
210 for mat in matches {
211 any_matches.push(mat);
212 }
213 }
214 any_matches
215 })
216 }
217 fn active_match_index(
218 &self,
219 matches: &AnyVec<dyn Send>,
220 cx: &mut WindowContext,
221 ) -> Option<usize> {
222 let matches = matches.downcast_ref()?;
223 self.update(cx, |this, cx| {
224 this.active_match_index(matches.as_slice(), cx)
225 })
226 }
227
228 fn replace(
229 &self,
230 mat: any_vec::element::ElementRef<'_, dyn Send>,
231 query: &SearchQuery,
232 cx: &mut WindowContext,
233 ) {
234 let mat = mat.downcast_ref().unwrap();
235 self.update(cx, |this, cx| this.replace(mat, query, cx))
236 }
237
238 fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) {
239 self.update(cx, |this, cx| {
240 this.search_bar_visibility_changed(visible, cx)
241 });
242 }
243}
244
245impl From<Box<dyn SearchableItemHandle>> for AnyView {
246 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
247 this.to_any().clone()
248 }
249}
250
251impl From<&Box<dyn SearchableItemHandle>> for AnyView {
252 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
253 this.to_any().clone()
254 }
255}
256
257impl PartialEq for Box<dyn SearchableItemHandle> {
258 fn eq(&self, other: &Self) -> bool {
259 self.item_id() == other.item_id()
260 }
261}
262
263impl Eq for Box<dyn SearchableItemHandle> {}
264
265pub trait WeakSearchableItemHandle: WeakItemHandle {
266 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
267
268 fn into_any(self) -> AnyWeakView;
269}
270
271impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
272 fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
273 Some(Box::new(self.upgrade()?))
274 }
275
276 fn into_any(self) -> AnyWeakView {
277 self.into()
278 }
279}
280
281impl PartialEq for Box<dyn WeakSearchableItemHandle> {
282 fn eq(&self, other: &Self) -> bool {
283 self.id() == other.id()
284 }
285}
286
287impl Eq for Box<dyn WeakSearchableItemHandle> {}
288
289impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
290 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
291 self.id().hash(state)
292 }
293}