From 269d2513ca3c7d4aa8a2ce17e71841eaa6dc2b01 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 20 Mar 2024 23:13:58 +0800 Subject: [PATCH] Add support for applying theme after extension is installed (#9529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release Notes: - Added support for opening the theme selector with installed themes after installing an extension containing themes. ([#9228](https://github.com/zed-industries/zed/issues/9228)). Screenshot 2024-03-20 at 11 00 35 AM --------- Co-authored-by: Marshall Bowers --- Cargo.lock | 2 + crates/collab_ui/src/collab_titlebar_item.rs | 4 +- crates/extension/src/extension_store.rs | 33 ++++++++++++++-- crates/extensions_ui/Cargo.toml | 1 + crates/extensions_ui/src/extensions_ui.rs | 34 +++++++++++++++- crates/theme_selector/Cargo.toml | 1 + crates/theme_selector/src/theme_selector.rs | 41 ++++++++++++++++---- crates/zed/src/zed/app_menus.rs | 2 +- 8 files changed, 102 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3addc6b9fa..0fb638cf77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3528,6 +3528,7 @@ dependencies = [ "settings", "smallvec", "theme", + "theme_selector", "ui", "util", "workspace", @@ -9619,6 +9620,7 @@ dependencies = [ "gpui", "log", "picker", + "serde", "settings", "theme", "ui", diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 9c210871da..7fd8b26b72 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -689,7 +689,7 @@ impl CollabTitlebarItem { ContextMenu::build(cx, |menu, _| { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Extensions", extensions_ui::Extensions.boxed_clone()) - .action("Themes...", theme_selector::Toggle.boxed_clone()) + .action("Themes...", theme_selector::Toggle::default().boxed_clone()) .separator() .action("Sign Out", client::SignOut.boxed_clone()) }) @@ -713,7 +713,7 @@ impl CollabTitlebarItem { ContextMenu::build(cx, |menu, _| { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Extensions", extensions_ui::Extensions.boxed_clone()) - .action("Themes...", theme_selector::Toggle.boxed_clone()) + .action("Themes...", theme_selector::Toggle::default().boxed_clone()) }) .into() }) diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index e627d8b9f3..7d0d278461 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -92,16 +92,18 @@ pub enum ExtensionStatus { Removing, } +#[derive(Clone, Copy)] enum ExtensionOperation { Upgrade, Install, Remove, } -#[derive(Copy, Clone)] +#[derive(Clone)] pub enum Event { ExtensionsUpdated, StartedReloading, + ExtensionInstalled(Arc), } impl EventEmitter for ExtensionStore {} @@ -330,6 +332,7 @@ impl ExtensionStore { .unbounded_send(modified_extension) .expect("reload task exited"); cx.emit(Event::StartedReloading); + async move { rx.await.ok(); } @@ -358,6 +361,17 @@ impl ExtensionStore { .filter_map(|extension| extension.dev.then_some(&extension.manifest)) } + /// Returns the names of themes provided by extensions. + pub fn extension_themes<'a>( + &'a self, + extension_id: &'a str, + ) -> impl Iterator> { + self.extension_index + .themes + .iter() + .filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name)) + } + pub fn fetch_extensions( &self, search: Option<&str>, @@ -441,8 +455,21 @@ impl ExtensionStore { archive .unpack(extensions_dir.join(extension_id.as_ref())) .await?; - this.update(&mut cx, |this, cx| this.reload(Some(extension_id), cx))? - .await; + this.update(&mut cx, |this, cx| { + this.reload(Some(extension_id.clone()), cx) + })? + .await; + + match operation { + ExtensionOperation::Install => { + this.update(&mut cx, |_, cx| { + cx.emit(Event::ExtensionInstalled(extension_id)); + }) + .ok(); + } + _ => {} + } + anyhow::Ok(()) }) .detach_and_log_err(cx); diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index 7f9535ddb0..fd0c721804 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -28,6 +28,7 @@ serde.workspace = true settings.workspace = true smallvec.workspace = true theme.workspace = true +theme_selector.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 89711f0dfc..5d7a7c62cd 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -9,7 +9,7 @@ use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle, FontWeight, InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle, - UniformListScrollHandle, View, ViewContext, VisualContext, WhiteSpace, WindowContext, + UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, }; use settings::Settings; use std::ops::DerefMut; @@ -100,10 +100,14 @@ impl ExtensionsPage { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> View { cx.new_view(|cx: &mut ViewContext| { let store = ExtensionStore::global(cx); + let workspace_handle = workspace.weak_handle(); let subscriptions = [ cx.observe(&store, |_, _, cx| cx.notify()), - cx.subscribe(&store, |this, _, event, cx| match event { + cx.subscribe(&store, move |this, _, event, cx| match event { extension::Event::ExtensionsUpdated => this.fetch_extensions_debounced(cx), + extension::Event::ExtensionInstalled(extension_id) => { + this.on_extension_installed(workspace_handle.clone(), extension_id, cx) + } _ => {} }), ]; @@ -133,6 +137,32 @@ impl ExtensionsPage { }) } + fn on_extension_installed( + &mut self, + workspace: WeakView, + extension_id: &str, + cx: &mut ViewContext, + ) { + let extension_store = ExtensionStore::global(cx).read(cx); + let themes = extension_store + .extension_themes(extension_id) + .map(|name| name.to_string()) + .collect::>(); + if !themes.is_empty() { + workspace + .update(cx, |workspace, cx| { + theme_selector::toggle( + workspace, + &theme_selector::Toggle { + themes_filter: Some(themes), + }, + cx, + ) + }) + .ok(); + } + } + fn filter_extension_entries(&mut self, cx: &mut ViewContext) { let extension_store = ExtensionStore::global(cx).read(cx); diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 3f9c9e6205..42087f2704 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -20,6 +20,7 @@ fuzzy.workspace = true gpui.workspace = true log.workspace = true picker.workspace = true +serde.workspace = true settings.workspace = true theme.workspace = true ui.workspace = true diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index c0008e90d6..2e0cb77495 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -3,10 +3,11 @@ use feature_flags::FeatureFlagAppExt; use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ - actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View, ViewContext, - VisualContext, WeakView, + actions, impl_actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View, + ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; +use serde::Deserialize; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{ @@ -16,7 +17,14 @@ use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; -actions!(theme_selector, [Toggle, Reload]); +#[derive(PartialEq, Clone, Default, Debug, Deserialize)] +pub struct Toggle { + /// A list of theme names to filter the theme selector down to. + pub themes_filter: Option>, +} + +impl_actions!(theme_selector, [Toggle]); +actions!(theme_selector, [Reload]); pub fn init(cx: &mut AppContext) { cx.observe_new_views( @@ -27,14 +35,18 @@ pub fn init(cx: &mut AppContext) { .detach(); } -pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { +pub fn toggle(workspace: &mut Workspace, toggle: &Toggle, cx: &mut ViewContext) { let fs = workspace.app_state().fs.clone(); let telemetry = workspace.client().telemetry().clone(); workspace.toggle_modal(cx, |cx| { - ThemeSelector::new( - ThemeSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx), + let delegate = ThemeSelectorDelegate::new( + cx.view().downgrade(), + fs, + telemetry, + toggle.themes_filter.as_ref(), cx, - ) + ); + ThemeSelector::new(delegate, cx) }); } @@ -81,13 +93,25 @@ impl ThemeSelectorDelegate { weak_view: WeakView, fs: Arc, telemetry: Arc, + themes_filter: Option<&Vec>, cx: &mut ViewContext, ) -> Self { let original_theme = cx.theme().clone(); let staff_mode = cx.is_staff(); let registry = ThemeRegistry::global(cx); - let mut themes = registry.list(staff_mode); + let mut themes = registry + .list(staff_mode) + .into_iter() + .filter(|meta| { + if let Some(theme_filter) = themes_filter { + theme_filter.contains(&meta.name.to_string()) + } else { + true + } + }) + .collect::>(); + themes.sort_unstable_by(|a, b| { a.appearance .is_light() @@ -113,6 +137,7 @@ impl ThemeSelectorDelegate { telemetry, view: weak_view, }; + this.select_if_matching(&original_theme.name); this } diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index fb06f4dcc5..c4425d983b 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -20,7 +20,7 @@ pub fn app_menus() -> Vec> { MenuItem::action("Open Default Settings", super::OpenDefaultSettings), MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap), MenuItem::action("Open Local Settings", super::OpenLocalSettings), - MenuItem::action("Select Theme...", theme_selector::Toggle), + MenuItem::action("Select Theme...", theme_selector::Toggle::default()), ], }), MenuItem::action("Extensions", extensions_ui::Extensions),