Skip to repository content566 lines · 15.6 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:07:25.287Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
searchable.rs
1use std::{any::Any, sync::Arc};
2
3use any_vec::AnyVec;
4use gpui::{
5 AnyView, AnyWeakEntity, App, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
6 Window,
7};
8use project::search::SearchQuery;
9use settings::SeedQuerySetting;
10
11use crate::{
12 ItemHandle,
13 item::{Item, WeakItemHandle},
14};
15
16#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
17pub struct SearchToken(u64);
18
19impl SearchToken {
20 pub fn new(value: u64) -> Self {
21 Self(value)
22 }
23
24 pub fn value(&self) -> u64 {
25 self.0
26 }
27}
28
29#[derive(Debug, Clone)]
30pub enum SearchEvent {
31 MatchesInvalidated,
32 ActiveMatchChanged,
33}
34
35#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
36pub enum Direction {
37 Prev,
38 #[default]
39 Next,
40}
41
42impl Direction {
43 pub fn opposite(&self) -> Self {
44 match self {
45 Direction::Prev => Direction::Next,
46 Direction::Next => Direction::Prev,
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Default)]
52pub struct SearchOptions {
53 pub case: bool,
54 pub word: bool,
55 pub regex: bool,
56 /// Specifies whether the supports search & replace.
57 pub replacement: bool,
58 pub selection: bool,
59 pub select_all: bool,
60 pub find_in_results: bool,
61}
62
63// Whether to always select the current selection (even if empty)
64// or to use the default (restoring the previous search ranges if some,
65// otherwise using the whole file).
66#[derive(Clone, Copy, Debug, Default, PartialEq)]
67pub enum FilteredSearchRange {
68 Selection,
69 #[default]
70 Default,
71}
72
73pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
74 type Match: Any + Sync + Send + Clone;
75
76 fn supported_options(&self) -> SearchOptions {
77 SearchOptions {
78 case: true,
79 word: true,
80 regex: true,
81 replacement: true,
82 selection: true,
83 select_all: true,
84 find_in_results: false,
85 }
86 }
87
88 fn search_bar_visibility_changed(
89 &mut self,
90 _visible: bool,
91 _window: &mut Window,
92 _cx: &mut Context<Self>,
93 ) {
94 }
95
96 fn has_filtered_search_ranges(&mut self) -> bool {
97 self.supported_options().selection
98 }
99
100 fn toggle_filtered_search_ranges(
101 &mut self,
102 _enabled: Option<FilteredSearchRange>,
103 _window: &mut Window,
104 _cx: &mut Context<Self>,
105 ) {
106 }
107
108 fn get_matches(&self, _window: &mut Window, _: &mut App) -> (Vec<Self::Match>, SearchToken) {
109 (Vec::new(), SearchToken::default())
110 }
111 fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>);
112 fn update_matches(
113 &mut self,
114 matches: &[Self::Match],
115 active_match_index: Option<usize>,
116 token: SearchToken,
117 window: &mut Window,
118 cx: &mut Context<Self>,
119 );
120 fn query_suggestion(
121 &mut self,
122 seed_query_override: Option<SeedQuerySetting>,
123 window: &mut Window,
124 cx: &mut Context<Self>,
125 ) -> String;
126 fn activate_match(
127 &mut self,
128 index: usize,
129 matches: &[Self::Match],
130 token: SearchToken,
131 window: &mut Window,
132 cx: &mut Context<Self>,
133 );
134 fn select_matches(
135 &mut self,
136 matches: &[Self::Match],
137 token: SearchToken,
138 window: &mut Window,
139 cx: &mut Context<Self>,
140 );
141 fn replace(
142 &mut self,
143 _: &Self::Match,
144 _: &SearchQuery,
145 _token: SearchToken,
146 _window: &mut Window,
147 _: &mut Context<Self>,
148 );
149 fn replace_all(
150 &mut self,
151 matches: &mut dyn Iterator<Item = &Self::Match>,
152 query: &SearchQuery,
153 token: SearchToken,
154 window: &mut Window,
155 cx: &mut Context<Self>,
156 ) {
157 for item in matches {
158 self.replace(item, query, token, window, cx);
159 }
160 }
161 fn match_index_for_direction(
162 &mut self,
163 matches: &[Self::Match],
164 current_index: usize,
165 direction: Direction,
166 count: usize,
167 _token: SearchToken,
168 _window: &mut Window,
169 _: &mut Context<Self>,
170 ) -> usize {
171 match direction {
172 Direction::Prev => {
173 let count = count % matches.len();
174 if current_index >= count {
175 current_index - count
176 } else {
177 matches.len() - (count - current_index)
178 }
179 }
180 Direction::Next => (current_index + count) % matches.len(),
181 }
182 }
183 fn find_matches(
184 &mut self,
185 query: Arc<SearchQuery>,
186 window: &mut Window,
187 cx: &mut Context<Self>,
188 ) -> Task<Vec<Self::Match>>;
189
190 fn find_matches_with_token(
191 &mut self,
192 query: Arc<SearchQuery>,
193 window: &mut Window,
194 cx: &mut Context<Self>,
195 ) -> Task<(Vec<Self::Match>, SearchToken)> {
196 let matches = self.find_matches(query, window, cx);
197 cx.spawn(async move |_, _| (matches.await, SearchToken::default()))
198 }
199
200 fn active_match_index(
201 &mut self,
202 direction: Direction,
203 matches: &[Self::Match],
204 token: SearchToken,
205 window: &mut Window,
206 cx: &mut Context<Self>,
207 ) -> Option<usize>;
208 fn set_search_is_case_sensitive(&mut self, _: Option<bool>, _: &mut Context<Self>) {}
209}
210
211pub trait SearchableItemHandle: ItemHandle {
212 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
213 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
214 fn supported_options(&self, cx: &App) -> SearchOptions;
215 fn subscribe_to_search_events(
216 &self,
217 window: &mut Window,
218 cx: &mut App,
219 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
220 ) -> Subscription;
221 fn clear_matches(&self, window: &mut Window, cx: &mut App);
222 fn update_matches(
223 &self,
224 matches: &AnyVec<dyn Send>,
225 active_match_index: Option<usize>,
226 token: SearchToken,
227 window: &mut Window,
228 cx: &mut App,
229 );
230 fn query_suggestion(
231 &self,
232 seed_query_override: Option<SeedQuerySetting>,
233 window: &mut Window,
234 cx: &mut App,
235 ) -> String;
236 fn activate_match(
237 &self,
238 index: usize,
239 matches: &AnyVec<dyn Send>,
240 token: SearchToken,
241 window: &mut Window,
242 cx: &mut App,
243 );
244 fn select_matches(
245 &self,
246 matches: &AnyVec<dyn Send>,
247 token: SearchToken,
248 window: &mut Window,
249 cx: &mut App,
250 );
251 fn replace(
252 &self,
253 _: any_vec::element::ElementRef<'_, dyn Send>,
254 _: &SearchQuery,
255 token: SearchToken,
256 _window: &mut Window,
257 _: &mut App,
258 );
259 fn replace_all(
260 &self,
261 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
262 query: &SearchQuery,
263 token: SearchToken,
264 window: &mut Window,
265 cx: &mut App,
266 );
267 fn match_index_for_direction(
268 &self,
269 matches: &AnyVec<dyn Send>,
270 current_index: usize,
271 direction: Direction,
272 count: usize,
273 token: SearchToken,
274 window: &mut Window,
275 cx: &mut App,
276 ) -> usize;
277 fn find_matches(
278 &self,
279 query: Arc<SearchQuery>,
280 window: &mut Window,
281 cx: &mut App,
282 ) -> Task<AnyVec<dyn Send>>;
283 fn find_matches_with_token(
284 &self,
285 query: Arc<SearchQuery>,
286 window: &mut Window,
287 cx: &mut App,
288 ) -> Task<(AnyVec<dyn Send>, SearchToken)>;
289 fn active_match_index(
290 &self,
291 direction: Direction,
292 matches: &AnyVec<dyn Send>,
293 token: SearchToken,
294 window: &mut Window,
295 cx: &mut App,
296 ) -> Option<usize>;
297 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App);
298
299 fn toggle_filtered_search_ranges(
300 &mut self,
301 enabled: Option<FilteredSearchRange>,
302 window: &mut Window,
303 cx: &mut App,
304 );
305
306 fn set_search_is_case_sensitive(&self, is_case_sensitive: Option<bool>, cx: &mut App);
307}
308
309impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
310 fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
311 Box::new(self.downgrade())
312 }
313
314 fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
315 Box::new(self.clone())
316 }
317
318 fn supported_options(&self, cx: &App) -> SearchOptions {
319 self.read(cx).supported_options()
320 }
321
322 fn subscribe_to_search_events(
323 &self,
324 window: &mut Window,
325 cx: &mut App,
326 handler: Box<dyn Fn(&SearchEvent, &mut Window, &mut App) + Send>,
327 ) -> Subscription {
328 window.subscribe(self, cx, move |_, event: &SearchEvent, window, cx| {
329 handler(event, window, cx)
330 })
331 }
332
333 fn clear_matches(&self, window: &mut Window, cx: &mut App) {
334 self.update(cx, |this, cx| this.clear_matches(window, cx));
335 }
336 fn update_matches(
337 &self,
338 matches: &AnyVec<dyn Send>,
339 active_match_index: Option<usize>,
340 token: SearchToken,
341 window: &mut Window,
342 cx: &mut App,
343 ) {
344 let matches = matches.downcast_ref().unwrap();
345 self.update(cx, |this, cx| {
346 this.update_matches(matches.as_slice(), active_match_index, token, window, cx)
347 });
348 }
349 fn query_suggestion(
350 &self,
351 seed_query_override: Option<SeedQuerySetting>,
352 window: &mut Window,
353 cx: &mut App,
354 ) -> String {
355 self.update(cx, |this, cx| {
356 this.query_suggestion(seed_query_override, window, cx)
357 })
358 }
359 fn activate_match(
360 &self,
361 index: usize,
362 matches: &AnyVec<dyn Send>,
363 token: SearchToken,
364 window: &mut Window,
365 cx: &mut App,
366 ) {
367 let matches = matches.downcast_ref().unwrap();
368 self.update(cx, |this, cx| {
369 this.activate_match(index, matches.as_slice(), token, window, cx)
370 });
371 }
372
373 fn select_matches(
374 &self,
375 matches: &AnyVec<dyn Send>,
376 token: SearchToken,
377 window: &mut Window,
378 cx: &mut App,
379 ) {
380 let matches = matches.downcast_ref().unwrap();
381 self.update(cx, |this, cx| {
382 this.select_matches(matches.as_slice(), token, window, cx)
383 });
384 }
385
386 fn match_index_for_direction(
387 &self,
388 matches: &AnyVec<dyn Send>,
389 current_index: usize,
390 direction: Direction,
391 count: usize,
392 token: SearchToken,
393 window: &mut Window,
394 cx: &mut App,
395 ) -> usize {
396 let matches = matches.downcast_ref().unwrap();
397 self.update(cx, |this, cx| {
398 this.match_index_for_direction(
399 matches.as_slice(),
400 current_index,
401 direction,
402 count,
403 token,
404 window,
405 cx,
406 )
407 })
408 }
409 fn find_matches(
410 &self,
411 query: Arc<SearchQuery>,
412 window: &mut Window,
413 cx: &mut App,
414 ) -> Task<AnyVec<dyn Send>> {
415 let matches = self.update(cx, |this, cx| this.find_matches(query, window, cx));
416 window.spawn(cx, async |_| {
417 let matches = matches.await;
418 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
419 {
420 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
421 for mat in matches {
422 any_matches.push(mat);
423 }
424 }
425 any_matches
426 })
427 }
428 fn find_matches_with_token(
429 &self,
430 query: Arc<SearchQuery>,
431 window: &mut Window,
432 cx: &mut App,
433 ) -> Task<(AnyVec<dyn Send>, SearchToken)> {
434 let matches_with_token = self.update(cx, |this, cx| {
435 this.find_matches_with_token(query, window, cx)
436 });
437 window.spawn(cx, async |_| {
438 let (matches, token) = matches_with_token.await;
439 let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
440 {
441 let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
442 for mat in matches {
443 any_matches.push(mat);
444 }
445 }
446 (any_matches, token)
447 })
448 }
449 fn active_match_index(
450 &self,
451 direction: Direction,
452 matches: &AnyVec<dyn Send>,
453 token: SearchToken,
454 window: &mut Window,
455 cx: &mut App,
456 ) -> Option<usize> {
457 let matches = matches.downcast_ref()?;
458 self.update(cx, |this, cx| {
459 this.active_match_index(direction, matches.as_slice(), token, window, cx)
460 })
461 }
462
463 fn replace(
464 &self,
465 mat: any_vec::element::ElementRef<'_, dyn Send>,
466 query: &SearchQuery,
467 token: SearchToken,
468 window: &mut Window,
469 cx: &mut App,
470 ) {
471 let mat = mat.downcast_ref().unwrap();
472 self.update(cx, |this, cx| this.replace(mat, query, token, window, cx))
473 }
474
475 fn replace_all(
476 &self,
477 matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
478 query: &SearchQuery,
479 token: SearchToken,
480 window: &mut Window,
481 cx: &mut App,
482 ) {
483 self.update(cx, |this, cx| {
484 this.replace_all(
485 &mut matches.map(|m| m.downcast_ref().unwrap()),
486 query,
487 token,
488 window,
489 cx,
490 );
491 })
492 }
493
494 fn search_bar_visibility_changed(&self, visible: bool, window: &mut Window, cx: &mut App) {
495 self.update(cx, |this, cx| {
496 this.search_bar_visibility_changed(visible, window, cx)
497 });
498 }
499
500 fn toggle_filtered_search_ranges(
501 &mut self,
502 enabled: Option<FilteredSearchRange>,
503 window: &mut Window,
504 cx: &mut App,
505 ) {
506 self.update(cx, |this, cx| {
507 this.toggle_filtered_search_ranges(enabled, window, cx)
508 });
509 }
510 fn set_search_is_case_sensitive(&self, enabled: Option<bool>, cx: &mut App) {
511 self.update(cx, |this, cx| {
512 this.set_search_is_case_sensitive(enabled, cx)
513 });
514 }
515}
516
517impl From<Box<dyn SearchableItemHandle>> for AnyView {
518 fn from(this: Box<dyn SearchableItemHandle>) -> Self {
519 this.to_any_view()
520 }
521}
522
523impl From<&Box<dyn SearchableItemHandle>> for AnyView {
524 fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
525 this.to_any_view()
526 }
527}
528
529impl PartialEq for Box<dyn SearchableItemHandle> {
530 fn eq(&self, other: &Self) -> bool {
531 self.item_id() == other.item_id()
532 }
533}
534
535impl Eq for Box<dyn SearchableItemHandle> {}
536
537pub trait WeakSearchableItemHandle: WeakItemHandle {
538 fn upgrade(&self, cx: &App) -> Option<Box<dyn SearchableItemHandle>>;
539
540 fn into_any(self) -> AnyWeakEntity;
541}
542
543impl<T: SearchableItem> WeakSearchableItemHandle for WeakEntity<T> {
544 fn upgrade(&self, _cx: &App) -> Option<Box<dyn SearchableItemHandle>> {
545 Some(Box::new(self.upgrade()?))
546 }
547
548 fn into_any(self) -> AnyWeakEntity {
549 self.into()
550 }
551}
552
553impl PartialEq for Box<dyn WeakSearchableItemHandle> {
554 fn eq(&self, other: &Self) -> bool {
555 self.id() == other.id()
556 }
557}
558
559impl Eq for Box<dyn WeakSearchableItemHandle> {}
560
561impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
562 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
563 self.id().hash(state)
564 }
565}
566