Skip to repository content256 lines · 8.0 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:36:24.112Z 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
extension_version_selector.rs
1use std::str::FromStr;
2use std::sync::Arc;
3
4use cloud_api_types::ExtensionMetadata;
5use extension_host::ExtensionStore;
6use fs::Fs;
7use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
8use gpui::{App, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, prelude::*};
9use picker::{Picker, PickerDelegate};
10use release_channel::ReleaseChannel;
11use semver::Version;
12use settings::update_settings_file;
13use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
14use util::ResultExt;
15use workspace::ModalView;
16
17pub struct ExtensionVersionSelector {
18 picker: Entity<Picker<ExtensionVersionSelectorDelegate>>,
19}
20
21impl ModalView for ExtensionVersionSelector {}
22
23impl EventEmitter<DismissEvent> for ExtensionVersionSelector {}
24
25impl Focusable for ExtensionVersionSelector {
26 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
27 self.picker.focus_handle(cx)
28 }
29}
30
31impl Render for ExtensionVersionSelector {
32 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
33 v_flex().child(self.picker.clone())
34 }
35}
36
37impl ExtensionVersionSelector {
38 pub fn new(
39 delegate: ExtensionVersionSelectorDelegate,
40 window: &mut Window,
41 cx: &mut Context<Self>,
42 ) -> Self {
43 let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
44 Self { picker }
45 }
46}
47
48pub struct ExtensionVersionSelectorDelegate {
49 fs: Arc<dyn Fs>,
50 selector: WeakEntity<ExtensionVersionSelector>,
51 extension_versions: Vec<ExtensionMetadata>,
52 selected_index: usize,
53 matches: Vec<StringMatch>,
54}
55
56impl ExtensionVersionSelectorDelegate {
57 pub fn new(
58 fs: Arc<dyn Fs>,
59 selector: WeakEntity<ExtensionVersionSelector>,
60 mut extension_versions: Vec<ExtensionMetadata>,
61 ) -> Self {
62 extension_versions.sort_unstable_by(|a, b| {
63 let a_version = Version::from_str(&a.manifest.version);
64 let b_version = Version::from_str(&b.manifest.version);
65
66 match (a_version, b_version) {
67 (Ok(a_version), Ok(b_version)) => b_version.cmp(&a_version),
68 _ => b.published_at.cmp(&a.published_at),
69 }
70 });
71
72 let matches = extension_versions
73 .iter()
74 .map(|extension| StringMatch {
75 candidate_id: 0,
76 score: 0.0,
77 positions: Default::default(),
78 string: format!("v{}", extension.manifest.version),
79 })
80 .collect();
81
82 Self {
83 fs,
84 selector,
85 extension_versions,
86 selected_index: 0,
87 matches,
88 }
89 }
90}
91
92impl PickerDelegate for ExtensionVersionSelectorDelegate {
93 type ListItem = ui::ListItem;
94
95 fn name() -> &'static str {
96 "extension version selector"
97 }
98
99 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
100 "Select extension version...".into()
101 }
102
103 fn match_count(&self) -> usize {
104 self.matches.len()
105 }
106
107 fn selected_index(&self) -> usize {
108 self.selected_index
109 }
110
111 fn set_selected_index(
112 &mut self,
113 ix: usize,
114 _window: &mut Window,
115 _cx: &mut Context<Picker<Self>>,
116 ) {
117 self.selected_index = ix;
118 }
119
120 fn update_matches(
121 &mut self,
122 query: String,
123 window: &mut Window,
124 cx: &mut Context<Picker<Self>>,
125 ) -> Task<()> {
126 let background_executor = cx.background_executor().clone();
127 let candidates = self
128 .extension_versions
129 .iter()
130 .enumerate()
131 .map(|(id, extension)| {
132 StringMatchCandidate::new(id, &format!("v{}", extension.manifest.version))
133 })
134 .collect::<Vec<_>>();
135
136 cx.spawn_in(window, async move |this, cx| {
137 let matches = if query.is_empty() {
138 candidates
139 .into_iter()
140 .enumerate()
141 .map(|(index, candidate)| StringMatch {
142 candidate_id: index,
143 string: candidate.string,
144 positions: Vec::new(),
145 score: 0.0,
146 })
147 .collect()
148 } else {
149 match_strings(
150 &candidates,
151 &query,
152 false,
153 true,
154 100,
155 &Default::default(),
156 background_executor,
157 )
158 .await
159 };
160
161 this.update(cx, |this, _cx| {
162 this.delegate.matches = matches;
163 this.delegate.selected_index = this
164 .delegate
165 .selected_index
166 .min(this.delegate.matches.len().saturating_sub(1));
167 })
168 .log_err();
169 })
170 }
171
172 fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
173 if self.matches.is_empty() {
174 self.dismissed(window, cx);
175 return;
176 }
177
178 let candidate_id = self.matches[self.selected_index].candidate_id;
179 let extension_version = &self.extension_versions[candidate_id];
180
181 if !extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version) {
182 return;
183 }
184
185 let extension_store = ExtensionStore::global(cx);
186 extension_store.update(cx, |store, cx| {
187 let extension_id = extension_version.id.clone();
188 let version = extension_version.manifest.version.clone();
189
190 update_settings_file(self.fs.clone(), cx, {
191 let extension_id = extension_id.clone();
192 move |settings, _| {
193 settings
194 .extension
195 .auto_update_extensions
196 .insert(extension_id, false);
197 }
198 });
199
200 store.install_extension(extension_id, version, cx);
201 });
202 }
203
204 fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
205 self.selector
206 .update(cx, |_, cx| cx.emit(DismissEvent))
207 .log_err();
208 }
209
210 fn render_match(
211 &self,
212 ix: usize,
213 selected: bool,
214 _: &mut Window,
215 cx: &mut Context<Picker<Self>>,
216 ) -> Option<Self::ListItem> {
217 let version_match = &self.matches.get(ix)?;
218 let extension_version = &self.extension_versions.get(version_match.candidate_id)?;
219
220 let is_version_compatible =
221 extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version);
222 let disabled = !is_version_compatible;
223
224 Some(
225 ListItem::new(ix)
226 .inset(true)
227 .spacing(ListItemSpacing::Sparse)
228 .toggle_state(selected)
229 .disabled(disabled)
230 .child(
231 HighlightedLabel::new(
232 version_match.string.clone(),
233 version_match.positions.clone(),
234 )
235 .when(disabled, |label| label.color(Color::Muted)),
236 )
237 .end_slot(
238 h_flex()
239 .gap_2()
240 .when(!is_version_compatible, |this| {
241 this.child(Label::new("Incompatible").color(Color::Muted))
242 })
243 .child(
244 Label::new(
245 extension_version
246 .published_at
247 .format("%Y-%m-%d")
248 .to_string(),
249 )
250 .when(disabled, |label| label.color(Color::Muted)),
251 ),
252 ),
253 )
254 }
255}
256