1use std::any::Any;
2
3use gpui::{
4 AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
5 WeakViewHandle, WindowContext,
6};
7use project::search::SearchQuery;
8
9use crate::{item::WeakItemHandle, Item, ItemHandle};
10
11#[derive(Debug)]
12pub enum SearchEvent {
13 MatchesInvalidated,
14 ActiveMatchChanged,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18pub enum Direction {
19 Prev,
20 Next,
21}
22
23#[derive(Clone, Copy, Debug, Default)]
24pub struct SearchOptions {
25 pub case: bool,
26 pub word: bool,
27 pub regex: bool,
28}
29
30pub trait SearchableItem: Item {
31 type Match: Any + Sync + Send + Clone;
32
33 fn supported_options() -> SearchOptions {
34 SearchOptions {
35 case: true,
36 word: true,
37 regex: true,
38 }
39 }
40 fn to_search_event(event: &Self::Event) -> Option<SearchEvent>;
41 fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
42 fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
43 fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
44 fn activate_match(
45 &mut self,
46 index: usize,
47 matches: Vec<Self::Match>,
48 cx: &mut ViewContext<Self>,
49 );
50 fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
51 fn match_index_for_direction(
52 &mut self,
53 matches: &Vec<Self::Match>,
54 mut current_index: usize,
55 direction: Direction,
56 _: &mut ViewContext<Self>,
57 ) -> usize {
58 match direction {
59 Direction::Prev => {
60 if current_index == 0 {
61 matches.len() - 1
62 } else {
63 current_index - 1
64 }
65 }
66 Direction::Next => {
67 current_index += 1;
68 if current_index == matches.len() {
69 0
70 } else {
71 current_index
72 }
73 }
74 }
75 }
76 fn find_matches(
77 &mut self,
78 query: SearchQuery,
79 cx: &mut ViewContext<Self>,
80 ) -> Task<Vec<Self::Match>>;
81 fn active_match_index(
82 &mut self,
83 matches: Vec<Self::Match>,
84 cx: &mut ViewContext<Self>,
85 ) -> Option<usize>;
86}
87
88pub trait SearchableItemHandle: ItemHandle {
89 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
90 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
91 fn supported_options(&self) -> SearchOptions;
92 fn subscribe_to_search_events(
93 &self,
94 cx: &mut WindowContext,
95 handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
96 ) -> Subscription;
97 fn clear_matches(&self, cx: &mut WindowContext);
98 fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
99 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
100 fn activate_match(
101 &self,
102 index: usize,
103 matches: &Vec<Box<dyn Any + Send>>,
104 cx: &mut WindowContext,
105 );
106 fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
107 fn match_index_for_direction(
108 &self,
109 matches: &Vec<Box<dyn Any + Send>>,
110 current_index: usize,
111 direction: Direction,
112 cx: &mut WindowContext,
113 ) -> usize;
114 fn find_matches(
115 &self,
116 query: SearchQuery,
117 cx: &mut WindowContext,
118 ) -> Task<Vec<Box<dyn Any + Send>>>;
119 fn active_match_index(
120 &self,
121 matches: &Vec<Box<dyn Any + Send>>,
122 cx: &mut WindowContext,
123 ) -> Option<usize>;
124}
125
126impl<T: SearchableItem> SearchableItemHandle for ViewHandle<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)>,
143 ) -> Subscription {
144 cx.subscribe(self, move |_, event, cx| {
145 if let Some(search_event) = T::to_search_event(event) {
146 handler(search_event, cx)
147 }
148 })
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 cx: &mut WindowContext,
182 ) -> usize {
183 let matches = downcast_matches(matches);
184 self.update(cx, |this, cx| {
185 this.match_index_for_direction(&matches, current_index, direction, cx)
186 })
187 }
188 fn find_matches(
189 &self,
190 query: SearchQuery,
191 cx: &mut WindowContext,
192 ) -> Task<Vec<Box<dyn Any + Send>>> {
193 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
194 cx.foreground().spawn(async {
195 let matches = matches.await;
196 matches
197 .into_iter()
198 .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
199 .collect()
200 })
201 }
202 fn active_match_index(
203 &self,
204 matches: &Vec<Box<dyn Any + Send>>,
205 cx: &mut WindowContext,
206 ) -> Option<usize> {
207 let matches = downcast_matches(matches);
208 self.update(cx, |this, cx| this.active_match_index(matches, cx))
209 }
210}
211
212fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
213 matches
214 .iter()
215 .map(|range| range.downcast_ref::<T>().cloned())
216 .collect::<Option<Vec<_>>>()
217 .expect(
218 "SearchableItemHandle function called with vec of matches of a different type than expected",
219 )
220}
221
222impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
223 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
224 this.as_any().clone()
225 }
226}
227
228impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
229 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
230 this.as_any().clone()
231 }
232}
233
234impl PartialEq for Box<dyn SearchableItemHandle> {
235 fn eq(&self, other: &Self) -> bool {
236 self.id() == other.id() && self.window_id() == other.window_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) -> AnyWeakViewHandle;
246}
247
248impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
249 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
250 Some(Box::new(self.upgrade(cx)?))
251 }
252
253 fn into_any(self) -> AnyWeakViewHandle {
254 self.into_any()
255 }
256}
257
258impl PartialEq for Box<dyn WeakSearchableItemHandle> {
259 fn eq(&self, other: &Self) -> bool {
260 self.id() == other.id() && self.window_id() == other.window_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(), self.window_id()).hash(state)
269 }
270}