mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 18:03:02 +03:00
Add support for applying theme after extension is installed (#9529)
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)). <img width="1315" alt="Screenshot 2024-03-20 at 11 00 35 AM" src="https://github.com/zed-industries/zed/assets/1486634/593389b3-eade-4bce-ae17-25c02a074f21"> --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
parent
6cec389125
commit
269d2513ca
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3528,6 +3528,7 @@ dependencies = [
|
|||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"theme",
|
"theme",
|
||||||
|
"theme_selector",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
@ -9619,6 +9620,7 @@ dependencies = [
|
|||||||
"gpui",
|
"gpui",
|
||||||
"log",
|
"log",
|
||||||
"picker",
|
"picker",
|
||||||
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
|
@ -689,7 +689,7 @@ impl CollabTitlebarItem {
|
|||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Extensions", extensions_ui::Extensions.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()
|
.separator()
|
||||||
.action("Sign Out", client::SignOut.boxed_clone())
|
.action("Sign Out", client::SignOut.boxed_clone())
|
||||||
})
|
})
|
||||||
@ -713,7 +713,7 @@ impl CollabTitlebarItem {
|
|||||||
ContextMenu::build(cx, |menu, _| {
|
ContextMenu::build(cx, |menu, _| {
|
||||||
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
|
||||||
.action("Extensions", extensions_ui::Extensions.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()
|
.into()
|
||||||
})
|
})
|
||||||
|
@ -92,16 +92,18 @@ pub enum ExtensionStatus {
|
|||||||
Removing,
|
Removing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
enum ExtensionOperation {
|
enum ExtensionOperation {
|
||||||
Upgrade,
|
Upgrade,
|
||||||
Install,
|
Install,
|
||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ExtensionsUpdated,
|
ExtensionsUpdated,
|
||||||
StartedReloading,
|
StartedReloading,
|
||||||
|
ExtensionInstalled(Arc<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<Event> for ExtensionStore {}
|
impl EventEmitter<Event> for ExtensionStore {}
|
||||||
@ -330,6 +332,7 @@ impl ExtensionStore {
|
|||||||
.unbounded_send(modified_extension)
|
.unbounded_send(modified_extension)
|
||||||
.expect("reload task exited");
|
.expect("reload task exited");
|
||||||
cx.emit(Event::StartedReloading);
|
cx.emit(Event::StartedReloading);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
rx.await.ok();
|
rx.await.ok();
|
||||||
}
|
}
|
||||||
@ -358,6 +361,17 @@ impl ExtensionStore {
|
|||||||
.filter_map(|extension| extension.dev.then_some(&extension.manifest))
|
.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<Item = &'a Arc<str>> {
|
||||||
|
self.extension_index
|
||||||
|
.themes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fetch_extensions(
|
pub fn fetch_extensions(
|
||||||
&self,
|
&self,
|
||||||
search: Option<&str>,
|
search: Option<&str>,
|
||||||
@ -441,8 +455,21 @@ impl ExtensionStore {
|
|||||||
archive
|
archive
|
||||||
.unpack(extensions_dir.join(extension_id.as_ref()))
|
.unpack(extensions_dir.join(extension_id.as_ref()))
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| this.reload(Some(extension_id), cx))?
|
this.update(&mut cx, |this, cx| {
|
||||||
.await;
|
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(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
@ -28,6 +28,7 @@ serde.workspace = true
|
|||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
theme_selector.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
@ -9,7 +9,7 @@ use fuzzy::{match_strings, StringMatchCandidate};
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
|
actions, canvas, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
|
||||||
FontWeight, InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
|
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 settings::Settings;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
@ -100,10 +100,14 @@ impl ExtensionsPage {
|
|||||||
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||||
let store = ExtensionStore::global(cx);
|
let store = ExtensionStore::global(cx);
|
||||||
|
let workspace_handle = workspace.weak_handle();
|
||||||
let subscriptions = [
|
let subscriptions = [
|
||||||
cx.observe(&store, |_, _, cx| cx.notify()),
|
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::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<Workspace>,
|
||||||
|
extension_id: &str,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||||
|
let themes = extension_store
|
||||||
|
.extension_themes(extension_id)
|
||||||
|
.map(|name| name.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
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<Self>) {
|
fn filter_extension_entries(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let extension_store = ExtensionStore::global(cx).read(cx);
|
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ fuzzy.workspace = true
|
|||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
@ -3,10 +3,11 @@ use feature_flags::FeatureFlagAppExt;
|
|||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View, ViewContext,
|
actions, impl_actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, View,
|
||||||
VisualContext, WeakView,
|
ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
|
use serde::Deserialize;
|
||||||
use settings::{update_settings_file, SettingsStore};
|
use settings::{update_settings_file, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{
|
use theme::{
|
||||||
@ -16,7 +17,14 @@ use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
|
|||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ui::HighlightedLabel, ModalView, Workspace};
|
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<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_actions!(theme_selector, [Toggle]);
|
||||||
|
actions!(theme_selector, [Reload]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(
|
cx.observe_new_views(
|
||||||
@ -27,14 +35,18 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
pub fn toggle(workspace: &mut Workspace, toggle: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||||
let fs = workspace.app_state().fs.clone();
|
let fs = workspace.app_state().fs.clone();
|
||||||
let telemetry = workspace.client().telemetry().clone();
|
let telemetry = workspace.client().telemetry().clone();
|
||||||
workspace.toggle_modal(cx, |cx| {
|
workspace.toggle_modal(cx, |cx| {
|
||||||
ThemeSelector::new(
|
let delegate = ThemeSelectorDelegate::new(
|
||||||
ThemeSelectorDelegate::new(cx.view().downgrade(), fs, telemetry, cx),
|
cx.view().downgrade(),
|
||||||
|
fs,
|
||||||
|
telemetry,
|
||||||
|
toggle.themes_filter.as_ref(),
|
||||||
cx,
|
cx,
|
||||||
)
|
);
|
||||||
|
ThemeSelector::new(delegate, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +93,25 @@ impl ThemeSelectorDelegate {
|
|||||||
weak_view: WeakView<ThemeSelector>,
|
weak_view: WeakView<ThemeSelector>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
|
themes_filter: Option<&Vec<String>>,
|
||||||
cx: &mut ViewContext<ThemeSelector>,
|
cx: &mut ViewContext<ThemeSelector>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let original_theme = cx.theme().clone();
|
let original_theme = cx.theme().clone();
|
||||||
|
|
||||||
let staff_mode = cx.is_staff();
|
let staff_mode = cx.is_staff();
|
||||||
let registry = ThemeRegistry::global(cx);
|
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::<Vec<_>>();
|
||||||
|
|
||||||
themes.sort_unstable_by(|a, b| {
|
themes.sort_unstable_by(|a, b| {
|
||||||
a.appearance
|
a.appearance
|
||||||
.is_light()
|
.is_light()
|
||||||
@ -113,6 +137,7 @@ impl ThemeSelectorDelegate {
|
|||||||
telemetry,
|
telemetry,
|
||||||
view: weak_view,
|
view: weak_view,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.select_if_matching(&original_theme.name);
|
this.select_if_matching(&original_theme.name);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
|||||||
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
||||||
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
||||||
MenuItem::action("Open Local Settings", super::OpenLocalSettings),
|
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),
|
MenuItem::action("Extensions", extensions_ui::Extensions),
|
||||||
|
Loading…
Reference in New Issue
Block a user