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 match_index_for_direction(
51 &mut self,
52 matches: &Vec<Self::Match>,
53 mut current_index: usize,
54 direction: Direction,
55 _: &mut ViewContext<Self>,
56 ) -> usize {
57 match direction {
58 Direction::Prev => {
59 if current_index == 0 {
60 matches.len() - 1
61 } else {
62 current_index - 1
63 }
64 }
65 Direction::Next => {
66 current_index += 1;
67 if current_index == matches.len() {
68 0
69 } else {
70 current_index
71 }
72 }
73 }
74 }
75 fn find_matches(
76 &mut self,
77 query: SearchQuery,
78 cx: &mut ViewContext<Self>,
79 ) -> Task<Vec<Self::Match>>;
80 fn active_match_index(
81 &mut self,
82 matches: Vec<Self::Match>,
83 cx: &mut ViewContext<Self>,
84 ) -> Option<usize>;
85}
86
87pub trait SearchableItemHandle: ItemHandle {
88 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
89 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
90 fn supported_options(&self) -> SearchOptions;
91 fn subscribe_to_search_events(
92 &self,
93 cx: &mut WindowContext,
94 handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
95 ) -> Subscription;
96 fn clear_matches(&self, cx: &mut WindowContext);
97 fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
98 fn query_suggestion(&self, cx: &mut WindowContext) -> String;
99 fn activate_match(
100 &self,
101 index: usize,
102 matches: &Vec<Box<dyn Any + Send>>,
103 cx: &mut WindowContext,
104 );
105 fn match_index_for_direction(
106 &self,
107 matches: &Vec<Box<dyn Any + Send>>,
108 current_index: usize,
109 direction: Direction,
110 cx: &mut WindowContext,
111 ) -> usize;
112 fn find_matches(
113 &self,
114 query: SearchQuery,
115 cx: &mut WindowContext,
116 ) -> Task<Vec<Box<dyn Any + Send>>>;
117 fn active_match_index(
118 &self,
119 matches: &Vec<Box<dyn Any + Send>>,
120 cx: &mut WindowContext,
121 ) -> Option<usize>;
122}
123
124impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
125 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
126 Box::new(self.downgrade())
127 }
128
129 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
130 Box::new(self.clone())
131 }
132
133 fn supported_options(&self) -> SearchOptions {
134 T::supported_options()
135 }
136
137 fn subscribe_to_search_events(
138 &self,
139 cx: &mut WindowContext,
140 handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
141 ) -> Subscription {
142 cx.subscribe(self, move |_, event, cx| {
143 if let Some(search_event) = T::to_search_event(event) {
144 handler(search_event, cx)
145 }
146 })
147 }
148
149 fn clear_matches(&self, cx: &mut WindowContext) {
150 self.update(cx, |this, cx| this.clear_matches(cx));
151 }
152 fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
153 let matches = downcast_matches(matches);
154 self.update(cx, |this, cx| this.update_matches(matches, cx));
155 }
156 fn query_suggestion(&self, cx: &mut WindowContext) -> String {
157 self.update(cx, |this, cx| this.query_suggestion(cx))
158 }
159 fn activate_match(
160 &self,
161 index: usize,
162 matches: &Vec<Box<dyn Any + Send>>,
163 cx: &mut WindowContext,
164 ) {
165 let matches = downcast_matches(matches);
166 self.update(cx, |this, cx| this.activate_match(index, matches, cx));
167 }
168 fn match_index_for_direction(
169 &self,
170 matches: &Vec<Box<dyn Any + Send>>,
171 current_index: usize,
172 direction: Direction,
173 cx: &mut WindowContext,
174 ) -> usize {
175 let matches = downcast_matches(matches);
176 self.update(cx, |this, cx| {
177 this.match_index_for_direction(&matches, current_index, direction, cx)
178 })
179 }
180 fn find_matches(
181 &self,
182 query: SearchQuery,
183 cx: &mut WindowContext,
184 ) -> Task<Vec<Box<dyn Any + Send>>> {
185 let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
186 cx.foreground().spawn(async {
187 let matches = matches.await;
188 matches
189 .into_iter()
190 .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
191 .collect()
192 })
193 }
194 fn active_match_index(
195 &self,
196 matches: &Vec<Box<dyn Any + Send>>,
197 cx: &mut WindowContext,
198 ) -> Option<usize> {
199 let matches = downcast_matches(matches);
200 self.update(cx, |this, cx| this.active_match_index(matches, cx))
201 }
202}
203
204fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
205 matches
206 .iter()
207 .map(|range| range.downcast_ref::<T>().cloned())
208 .collect::<Option<Vec<_>>>()
209 .expect(
210 "SearchableItemHandle function called with vec of matches of a different type than expected",
211 )
212}
213
214impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
215 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
216 this.as_any().clone()
217 }
218}
219
220impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
221 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
222 this.as_any().clone()
223 }
224}
225
226impl PartialEq for Box<dyn SearchableItemHandle> {
227 fn eq(&self, other: &Self) -> bool {
228 self.id() == other.id() && self.window_id() == other.window_id()
229 }
230}
231
232impl Eq for Box<dyn SearchableItemHandle> {}
233
234pub trait WeakSearchableItemHandle: WeakItemHandle {
235 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
236
237 fn into_any(self) -> AnyWeakViewHandle;
238}
239
240impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
241 fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
242 Some(Box::new(self.upgrade(cx)?))
243 }
244
245 fn into_any(self) -> AnyWeakViewHandle {
246 self.into_any()
247 }
248}
249
250impl PartialEq for Box<dyn WeakSearchableItemHandle> {
251 fn eq(&self, other: &Self) -> bool {
252 self.id() == other.id() && self.window_id() == other.window_id()
253 }
254}
255
256impl Eq for Box<dyn WeakSearchableItemHandle> {}
257
258impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
259 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
260 (self.id(), self.window_id()).hash(state)
261 }
262}