From d7220670006e629c3c4dd3f3c955cf0da749cb14 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 19 Mar 2025 13:26:46 -0400 Subject: [PATCH] extensions_ui: Add ability to open the extensions view with a pre-selected filter (#27093) This PR adds the ability to open the extensions view via the `zed: extensions` action with a pre-selected filter. The "Install Themes" and "Install Icon Themes" buttons in their respective selectors take advantage of this to set the filter when opening the view: https://github.com/user-attachments/assets/2e345c0f-418a-47b6-811e-cabae6c616d1 Release Notes: - N/A --- crates/extensions_ui/src/extensions_ui.rs | 85 +++++++++++++------ .../theme_selector/src/icon_theme_selector.rs | 9 +- crates/theme_selector/src/theme_selector.rs | 9 +- crates/title_bar/src/title_bar.rs | 10 ++- crates/welcome/src/welcome.rs | 2 +- crates/zed/src/zed/app_menus.rs | 2 +- crates/zed_actions/src/lib.rs | 23 ++++- 7 files changed, 107 insertions(+), 33 deletions(-) diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 9f3197fb08..7fe504655d 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -28,6 +28,7 @@ use workspace::{ item::{Item, ItemEvent}, Workspace, WorkspaceId, }; +use zed_actions::ExtensionCategoryFilter; use crate::components::{ExtensionCard, FeatureUpsell}; use crate::extension_version_selector::{ @@ -42,26 +43,53 @@ pub fn init(cx: &mut App) { return; }; workspace - .register_action(move |workspace, _: &zed_actions::Extensions, window, cx| { - let existing = workspace - .active_pane() - .read(cx) - .items() - .find_map(|item| item.downcast::()); + .register_action( + move |workspace, action: &zed_actions::Extensions, window, cx| { + let provides_filter = action.category_filter.map(|category| match category { + ExtensionCategoryFilter::Themes => ExtensionProvides::Themes, + ExtensionCategoryFilter::IconThemes => ExtensionProvides::IconThemes, + ExtensionCategoryFilter::Languages => ExtensionProvides::Languages, + ExtensionCategoryFilter::Grammars => ExtensionProvides::Grammars, + ExtensionCategoryFilter::LanguageServers => { + ExtensionProvides::LanguageServers + } + ExtensionCategoryFilter::ContextServers => { + ExtensionProvides::ContextServers + } + ExtensionCategoryFilter::SlashCommands => ExtensionProvides::SlashCommands, + ExtensionCategoryFilter::IndexedDocsProviders => { + ExtensionProvides::IndexedDocsProviders + } + ExtensionCategoryFilter::Snippets => ExtensionProvides::Snippets, + }); - if let Some(existing) = existing { - workspace.activate_item(&existing, true, true, window, cx); - } else { - let extensions_page = ExtensionsPage::new(workspace, window, cx); - workspace.add_item_to_active_pane( - Box::new(extensions_page), - None, - true, - window, - cx, - ) - } - }) + let existing = workspace + .active_pane() + .read(cx) + .items() + .find_map(|item| item.downcast::()); + + if let Some(existing) = existing { + if provides_filter.is_some() { + existing.update(cx, |extensions_page, cx| { + extensions_page.change_provides_filter(provides_filter, cx); + }); + } + + workspace.activate_item(&existing, true, true, window, cx); + } else { + let extensions_page = + ExtensionsPage::new(workspace, provides_filter, window, cx); + workspace.add_item_to_active_pane( + Box::new(extensions_page), + None, + true, + window, + cx, + ) + } + }, + ) .register_action(move |workspace, _: &InstallDevExtension, window, cx| { let store = ExtensionStore::global(cx); let prompt = workspace.prompt_for_open_path( @@ -234,6 +262,7 @@ pub struct ExtensionsPage { impl ExtensionsPage { pub fn new( workspace: &Workspace, + provides_filter: Option, window: &mut Window, cx: &mut Context, ) -> Entity { @@ -277,13 +306,13 @@ impl ExtensionsPage { filtered_remote_extension_indices: Vec::new(), remote_extension_entries: Vec::new(), query_contains_error: false, - provides_filter: None, + provides_filter, extension_fetch_task: None, _subscriptions: subscriptions, query_editor, upsells: BTreeSet::default(), }; - this.fetch_extensions(None, None, cx); + this.fetch_extensions(None, Some(BTreeSet::from_iter(this.provides_filter)), cx); this }) } @@ -968,6 +997,15 @@ impl ExtensionsPage { self.refresh_feature_upsells(cx); } + pub fn change_provides_filter( + &mut self, + provides_filter: Option, + cx: &mut Context, + ) { + self.provides_filter = provides_filter; + self.refresh_search(cx); + } + fn fetch_extensions_debounced(&mut self, cx: &mut Context) { self.extension_fetch_task = Some(cx.spawn(async move |this, cx| { let search = this @@ -1155,8 +1193,7 @@ impl ExtensionsPage { let this = this.clone(); move |_window, cx| { this.update(cx, |this, cx| { - this.provides_filter = None; - this.refresh_search(cx); + this.change_provides_filter(None, cx); }); } }, @@ -1174,8 +1211,8 @@ impl ExtensionsPage { let this = this.clone(); move |_window, cx| { this.update(cx, |this, cx| { + this.change_provides_filter(Some(provides), cx); this.provides_filter = Some(provides); - this.refresh_search(cx); }); } }, diff --git a/crates/theme_selector/src/icon_theme_selector.rs b/crates/theme_selector/src/icon_theme_selector.rs index 860bdd585f..35fc9bab5c 100644 --- a/crates/theme_selector/src/icon_theme_selector.rs +++ b/crates/theme_selector/src/icon_theme_selector.rs @@ -11,7 +11,7 @@ use theme::{Appearance, IconTheme, ThemeMeta, ThemeRegistry, ThemeSettings}; use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView}; -use zed_actions::Extensions; +use zed_actions::{ExtensionCategoryFilter, Extensions}; pub(crate) struct IconThemeSelector { picker: Entity>, @@ -301,7 +301,12 @@ impl PickerDelegate for IconThemeSelectorDelegate { .child( Button::new("more-icon-themes", "Install Icon Themes").on_click( move |_event, window, cx| { - window.dispatch_action(Box::new(Extensions), cx); + window.dispatch_action( + Box::new(Extensions { + category_filter: Some(ExtensionCategoryFilter::IconThemes), + }), + cx, + ); }, ), ) diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index a1737a4f7b..f4787c6008 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -13,7 +13,7 @@ use theme::{Appearance, Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; -use zed_actions::Extensions; +use zed_actions::{ExtensionCategoryFilter, Extensions}; use crate::icon_theme_selector::{IconThemeSelector, IconThemeSelectorDelegate}; @@ -349,7 +349,12 @@ impl PickerDelegate for ThemeSelectorDelegate { .child( Button::new("more-themes", "Install Themes").on_click(cx.listener({ move |_, _, window, cx| { - window.dispatch_action(Box::new(Extensions), cx); + window.dispatch_action( + Box::new(Extensions { + category_filter: Some(ExtensionCategoryFilter::Themes), + }), + cx, + ); } })), ) diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 37852dd599..f71c230dfd 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -683,7 +683,10 @@ impl TitleBar { "Icon Themes…", zed_actions::icon_theme_selector::Toggle::default().boxed_clone(), ) - .action("Extensions", zed_actions::Extensions.boxed_clone()) + .action( + "Extensions", + zed_actions::Extensions::default().boxed_clone(), + ) .separator() .link( "Book Onboarding", @@ -730,7 +733,10 @@ impl TitleBar { "Icon Themes…", zed_actions::icon_theme_selector::Toggle::default().boxed_clone(), ) - .action("Extensions", zed_actions::Extensions.boxed_clone()) + .action( + "Extensions", + zed_actions::Extensions::default().boxed_clone(), + ) .separator() .link( "Book Onboarding", diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index ac36b9cf26..fa2b37e8f7 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -248,7 +248,7 @@ impl Render for WelcomePage { .on_click(cx.listener(|_, _, window, cx| { telemetry::event!("Welcome Extensions Page Opened"); window.dispatch_action(Box::new( - zed_actions::Extensions, + zed_actions::Extensions::default(), ), cx); })), ) diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index bf9aa8c525..ed590e9c24 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -35,7 +35,7 @@ pub fn app_menus() -> Vec { items: vec![], }), MenuItem::separator(), - MenuItem::action("Extensions", zed_actions::Extensions), + MenuItem::action("Extensions", zed_actions::Extensions::default()), MenuItem::action("Install CLI", install_cli::Install), MenuItem::separator(), #[cfg(target_os = "macos")] diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 4061cb6194..8463fc07d0 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -35,12 +35,32 @@ actions!( Quit, OpenKeymap, About, - Extensions, OpenLicenses, OpenTelemetryLog, ] ); +#[derive(PartialEq, Clone, Copy, Debug, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExtensionCategoryFilter { + Themes, + IconThemes, + Languages, + Grammars, + LanguageServers, + ContextServers, + SlashCommands, + IndexedDocsProviders, + Snippets, +} + +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +pub struct Extensions { + /// Filters the extensions page down to extensions that are in the specified category. + #[serde(default)] + pub category_filter: Option, +} + #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] pub struct DecreaseBufferFontSize { #[serde(default)] @@ -80,6 +100,7 @@ pub struct ResetUiFontSize { impl_actions!( zed, [ + Extensions, DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,