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