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