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