Skip to repository content185 lines · 5.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:35:32.049Z 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
theme_picker.rs
1use std::sync::Arc;
2
3use fuzzy::{StringMatch, StringMatchCandidate};
4use gpui::{AnyElement, App, Context, DismissEvent, SharedString, Task, Window};
5use picker::{Picker, PickerDelegate};
6use theme::ThemeRegistry;
7use ui::{ListItem, ListItemSpacing, prelude::*};
8
9type ThemePicker = Picker<ThemePickerDelegate>;
10
11pub struct ThemePickerDelegate {
12 themes: Vec<SharedString>,
13 filtered_themes: Vec<StringMatch>,
14 selected_index: usize,
15 current_theme: SharedString,
16 on_theme_changed: Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>,
17}
18
19impl ThemePickerDelegate {
20 fn new(
21 current_theme: SharedString,
22 on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
23 cx: &mut Context<ThemePicker>,
24 ) -> Self {
25 let theme_registry = ThemeRegistry::global(cx);
26
27 let themes = theme_registry.list_names();
28 let selected_index = themes
29 .iter()
30 .position(|theme| *theme == current_theme)
31 .unwrap_or(0);
32
33 let filtered_themes = themes
34 .iter()
35 .enumerate()
36 .map(|(index, theme)| StringMatch {
37 candidate_id: index,
38 string: theme.to_string(),
39 positions: Vec::new(),
40 score: 0.0,
41 })
42 .collect();
43
44 Self {
45 themes,
46 filtered_themes,
47 selected_index,
48 current_theme,
49 on_theme_changed: Arc::new(on_theme_changed),
50 }
51 }
52}
53
54impl PickerDelegate for ThemePickerDelegate {
55 type ListItem = AnyElement;
56
57 fn name() -> &'static str {
58 "theme picker"
59 }
60
61 fn match_count(&self) -> usize {
62 self.filtered_themes.len()
63 }
64
65 fn selected_index(&self) -> usize {
66 self.selected_index
67 }
68
69 fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<ThemePicker>) {
70 self.selected_index = ix.min(self.filtered_themes.len().saturating_sub(1));
71 cx.notify();
72 }
73
74 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
75 "Search theme…".into()
76 }
77
78 fn update_matches(
79 &mut self,
80 query: String,
81 _window: &mut Window,
82 cx: &mut Context<ThemePicker>,
83 ) -> Task<()> {
84 let themes = self.themes.clone();
85 let current_theme = self.current_theme.clone();
86
87 let matches: Vec<StringMatch> = if query.is_empty() {
88 themes
89 .iter()
90 .enumerate()
91 .map(|(index, theme)| StringMatch {
92 candidate_id: index,
93 string: theme.to_string(),
94 positions: Vec::new(),
95 score: 0.0,
96 })
97 .collect()
98 } else {
99 let _candidates: Vec<StringMatchCandidate> = themes
100 .iter()
101 .enumerate()
102 .map(|(id, theme)| StringMatchCandidate::new(id, theme.as_ref()))
103 .collect();
104
105 themes
106 .iter()
107 .enumerate()
108 .filter(|(_, theme)| theme.to_lowercase().contains(&query.to_lowercase()))
109 .map(|(index, theme)| StringMatch {
110 candidate_id: index,
111 string: theme.to_string(),
112 positions: Vec::new(),
113 score: 0.0,
114 })
115 .collect()
116 };
117
118 let selected_index = if query.is_empty() {
119 themes
120 .iter()
121 .position(|theme| *theme == current_theme)
122 .unwrap_or(0)
123 } else {
124 matches
125 .iter()
126 .position(|m| themes[m.candidate_id] == current_theme)
127 .unwrap_or(0)
128 };
129
130 self.filtered_themes = matches;
131 self.selected_index = selected_index;
132 cx.notify();
133
134 Task::ready(())
135 }
136
137 fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<ThemePicker>) {
138 if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
139 let theme = theme_match.string.clone();
140 (self.on_theme_changed)(theme.into(), window, cx);
141 }
142 }
143
144 fn dismissed(&mut self, window: &mut Window, cx: &mut Context<ThemePicker>) {
145 cx.defer_in(window, |picker, window, cx| {
146 picker.set_query("", window, cx);
147 });
148 cx.emit(DismissEvent);
149 }
150
151 fn render_match(
152 &self,
153 ix: usize,
154 selected: bool,
155 _window: &mut Window,
156 _cx: &mut Context<ThemePicker>,
157 ) -> Option<Self::ListItem> {
158 let theme_match = self.filtered_themes.get(ix)?;
159
160 Some(
161 ListItem::new(ix)
162 .inset(true)
163 .spacing(ListItemSpacing::Sparse)
164 .toggle_state(selected)
165 .child(Label::new(theme_match.string.clone()))
166 .into_any_element(),
167 )
168 }
169}
170
171pub fn theme_picker(
172 current_theme: SharedString,
173 on_theme_changed: impl Fn(SharedString, &mut Window, &mut App) + 'static,
174 window: &mut Window,
175 cx: &mut Context<ThemePicker>,
176) -> ThemePicker {
177 let delegate = ThemePickerDelegate::new(current_theme, on_theme_changed, cx);
178
179 Picker::uniform_list(delegate, window, cx)
180 .show_scrollbar(true)
181 .initial_width(rems_from_px(210.))
182 .max_height(rems(18.))
183 .popover()
184}
185