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