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