Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:36:24.112Z 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

extension_version_selector.rs

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