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