Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:55:54.760Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

searchable.rs

566 lines · 15.6 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.