From 4e5b11a0a70d05aa4324afa7d5193a000f8a53b7 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 5 Feb 2025 19:09:37 -0500 Subject: [PATCH] extensions_ui: Add general structure for filtering extensions by what they provide (#24325) This PR adds the general structure for filtering the extensions list by what the extensions provide. Currently flagged for Zed staff until we get some design direction on how best to present the filter. Release Notes: - N/A --- Cargo.lock | 1 + crates/extension_host/src/extension_host.rs | 15 +++++- crates/extensions_ui/Cargo.toml | 1 + crates/extensions_ui/src/extensions_ui.rs | 60 ++++++++++++++++++--- crates/rpc/src/extension.rs | 13 ++++- 5 files changed, 81 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 651ca95a2ae87b..06a48f8de738eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4490,6 +4490,7 @@ dependencies = [ "db", "editor", "extension_host", + "feature_flags", "fs", "fuzzy", "gpui", diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 35a58af2e4ca2c..69c26d44a46b61 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -8,8 +8,9 @@ mod extension_store_test; use anyhow::{anyhow, bail, Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; +use client::ExtensionProvides; use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; -use collections::{btree_map, BTreeMap, HashMap, HashSet}; +use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; pub use extension::ExtensionManifest; use extension::{ @@ -464,6 +465,7 @@ impl ExtensionStore { pub fn fetch_extensions( &self, search: Option<&str>, + provides_filter: Option<&BTreeSet>, cx: &mut Context, ) -> Task>> { let version = CURRENT_SCHEMA_VERSION.to_string(); @@ -472,6 +474,17 @@ impl ExtensionStore { query.push(("filter", search)); } + let provides_filter = provides_filter.map(|provides_filter| { + provides_filter + .iter() + .map(|provides| provides.to_string()) + .collect::>() + .join(",") + }); + if let Some(provides_filter) = provides_filter.as_deref() { + query.push(("provides", provides_filter)); + } + self.fetch_extensions_from_api("/extensions", &query, cx) } diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index 1df235c82209b5..afdb3bf0a359d9 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -18,6 +18,7 @@ collections.workspace = true db.workspace = true editor.workspace = true extension_host.workspace = true +feature_flags.workspace = true fs.workspace = true fuzzy.workspace = true gpui.workspace = true diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 6d9cfa3f4b353b..afca5616db7f0d 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -10,6 +10,7 @@ use client::{ExtensionMetadata, ExtensionProvides}; use collections::{BTreeMap, BTreeSet}; use editor::{Editor, EditorElement, EditorStyle}; use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore}; +use feature_flags::FeatureFlagAppExt as _; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, uniform_list, Action, App, ClipboardItem, Context, Entity, EventEmitter, Flatten, @@ -210,6 +211,7 @@ pub struct ExtensionsPage { filtered_remote_extension_indices: Vec, query_editor: Entity, query_contains_error: bool, + provides_filter: Option, _subscriptions: [gpui::Subscription; 2], extension_fetch_task: Option>, upsells: BTreeSet, @@ -261,12 +263,13 @@ impl ExtensionsPage { filtered_remote_extension_indices: Vec::new(), remote_extension_entries: Vec::new(), query_contains_error: false, + provides_filter: None, extension_fetch_task: None, _subscriptions: subscriptions, query_editor, upsells: BTreeSet::default(), }; - this.fetch_extensions(None, cx); + this.fetch_extensions(None, None, cx); this }) } @@ -363,7 +366,12 @@ impl ExtensionsPage { cx.notify(); } - fn fetch_extensions(&mut self, search: Option, cx: &mut Context) { + fn fetch_extensions( + &mut self, + search: Option, + provides_filter: Option>, + cx: &mut Context, + ) { self.is_fetching_extensions = true; cx.notify(); @@ -374,7 +382,7 @@ impl ExtensionsPage { }); let remote_extensions = extension_store.update(cx, |store, cx| { - store.fetch_extensions(search.as_deref(), cx) + store.fetch_extensions(search.as_deref(), provides_filter.as_ref(), cx) }); cx.spawn(move |this, mut cx| async move { @@ -953,11 +961,15 @@ impl ExtensionsPage { ) { if let editor::EditorEvent::Edited { .. } = event { self.query_contains_error = false; - self.fetch_extensions_debounced(cx); - self.refresh_feature_upsells(cx); + self.refresh_search(cx); } } + fn refresh_search(&mut self, cx: &mut Context) { + self.fetch_extensions_debounced(cx); + self.refresh_feature_upsells(cx); + } + fn fetch_extensions_debounced(&mut self, cx: &mut Context) { self.extension_fetch_task = Some(cx.spawn(|this, mut cx| async move { let search = this @@ -978,7 +990,7 @@ impl ExtensionsPage { }; this.update(&mut cx, |this, cx| { - this.fetch_extensions(search, cx); + this.fetch_extensions(search, Some(BTreeSet::from_iter(this.provides_filter)), cx); }) .ok(); })); @@ -1162,7 +1174,41 @@ impl Render for ExtensionsPage { .w_full() .gap_2() .justify_between() - .child(h_flex().child(self.render_search(cx))) + .child( + h_flex() + .gap_2() + .child(self.render_search(cx)) + .map(|parent| { + // Note: Staff-only until this gets design input. + if !cx.is_staff() { + return parent; + } + + parent.child(CheckboxWithLabel::new( + "icon-themes-filter", + Label::new("Icon themes"), + match self.provides_filter { + Some(ExtensionProvides::IconThemes) => { + ToggleState::Selected + } + _ => ToggleState::Unselected, + }, + cx.listener(|this, checked, _window, cx| { + match checked { + ToggleState::Unselected + | ToggleState::Indeterminate => { + this.provides_filter = None + } + ToggleState::Selected => { + this.provides_filter = + Some(ExtensionProvides::IconThemes) + } + }; + this.refresh_search(cx); + }), + )) + }), + ) .child( h_flex() .child( diff --git a/crates/rpc/src/extension.rs b/crates/rpc/src/extension.rs index f1dcdc28d66925..e8dd22b1bbae97 100644 --- a/crates/rpc/src/extension.rs +++ b/crates/rpc/src/extension.rs @@ -19,7 +19,18 @@ pub struct ExtensionApiManifest { } #[derive( - Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize, EnumString, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Clone, + Copy, + Serialize, + Deserialize, + EnumString, + strum::Display, )] #[serde(rename_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]