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
This commit is contained in:
parent
d51cd15e4d
commit
d722067000
7 changed files with 107 additions and 33 deletions
|
@ -28,6 +28,7 @@ use workspace::{
|
||||||
item::{Item, ItemEvent},
|
item::{Item, ItemEvent},
|
||||||
Workspace, WorkspaceId,
|
Workspace, WorkspaceId,
|
||||||
};
|
};
|
||||||
|
use zed_actions::ExtensionCategoryFilter;
|
||||||
|
|
||||||
use crate::components::{ExtensionCard, FeatureUpsell};
|
use crate::components::{ExtensionCard, FeatureUpsell};
|
||||||
use crate::extension_version_selector::{
|
use crate::extension_version_selector::{
|
||||||
|
@ -42,26 +43,53 @@ pub fn init(cx: &mut App) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
workspace
|
workspace
|
||||||
.register_action(move |workspace, _: &zed_actions::Extensions, window, cx| {
|
.register_action(
|
||||||
let existing = workspace
|
move |workspace, action: &zed_actions::Extensions, window, cx| {
|
||||||
.active_pane()
|
let provides_filter = action.category_filter.map(|category| match category {
|
||||||
.read(cx)
|
ExtensionCategoryFilter::Themes => ExtensionProvides::Themes,
|
||||||
.items()
|
ExtensionCategoryFilter::IconThemes => ExtensionProvides::IconThemes,
|
||||||
.find_map(|item| item.downcast::<ExtensionsPage>());
|
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 {
|
let existing = workspace
|
||||||
workspace.activate_item(&existing, true, true, window, cx);
|
.active_pane()
|
||||||
} else {
|
.read(cx)
|
||||||
let extensions_page = ExtensionsPage::new(workspace, window, cx);
|
.items()
|
||||||
workspace.add_item_to_active_pane(
|
.find_map(|item| item.downcast::<ExtensionsPage>());
|
||||||
Box::new(extensions_page),
|
|
||||||
None,
|
if let Some(existing) = existing {
|
||||||
true,
|
if provides_filter.is_some() {
|
||||||
window,
|
existing.update(cx, |extensions_page, cx| {
|
||||||
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| {
|
.register_action(move |workspace, _: &InstallDevExtension, window, cx| {
|
||||||
let store = ExtensionStore::global(cx);
|
let store = ExtensionStore::global(cx);
|
||||||
let prompt = workspace.prompt_for_open_path(
|
let prompt = workspace.prompt_for_open_path(
|
||||||
|
@ -234,6 +262,7 @@ pub struct ExtensionsPage {
|
||||||
impl ExtensionsPage {
|
impl ExtensionsPage {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
|
provides_filter: Option<ExtensionProvides>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
|
@ -277,13 +306,13 @@ impl ExtensionsPage {
|
||||||
filtered_remote_extension_indices: Vec::new(),
|
filtered_remote_extension_indices: Vec::new(),
|
||||||
remote_extension_entries: Vec::new(),
|
remote_extension_entries: Vec::new(),
|
||||||
query_contains_error: false,
|
query_contains_error: false,
|
||||||
provides_filter: None,
|
provides_filter,
|
||||||
extension_fetch_task: None,
|
extension_fetch_task: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
query_editor,
|
query_editor,
|
||||||
upsells: BTreeSet::default(),
|
upsells: BTreeSet::default(),
|
||||||
};
|
};
|
||||||
this.fetch_extensions(None, None, cx);
|
this.fetch_extensions(None, Some(BTreeSet::from_iter(this.provides_filter)), cx);
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -968,6 +997,15 @@ impl ExtensionsPage {
|
||||||
self.refresh_feature_upsells(cx);
|
self.refresh_feature_upsells(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn change_provides_filter(
|
||||||
|
&mut self,
|
||||||
|
provides_filter: Option<ExtensionProvides>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.provides_filter = provides_filter;
|
||||||
|
self.refresh_search(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch_extensions_debounced(&mut self, cx: &mut Context<ExtensionsPage>) {
|
fn fetch_extensions_debounced(&mut self, cx: &mut Context<ExtensionsPage>) {
|
||||||
self.extension_fetch_task = Some(cx.spawn(async move |this, cx| {
|
self.extension_fetch_task = Some(cx.spawn(async move |this, cx| {
|
||||||
let search = this
|
let search = this
|
||||||
|
@ -1155,8 +1193,7 @@ impl ExtensionsPage {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.provides_filter = None;
|
this.change_provides_filter(None, cx);
|
||||||
this.refresh_search(cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1174,8 +1211,8 @@ impl ExtensionsPage {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
this.change_provides_filter(Some(provides), cx);
|
||||||
this.provides_filter = Some(provides);
|
this.provides_filter = Some(provides);
|
||||||
this.refresh_search(cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ use theme::{Appearance, IconTheme, ThemeMeta, ThemeRegistry, ThemeSettings};
|
||||||
use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
|
use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ui::HighlightedLabel, ModalView};
|
use workspace::{ui::HighlightedLabel, ModalView};
|
||||||
use zed_actions::Extensions;
|
use zed_actions::{ExtensionCategoryFilter, Extensions};
|
||||||
|
|
||||||
pub(crate) struct IconThemeSelector {
|
pub(crate) struct IconThemeSelector {
|
||||||
picker: Entity<Picker<IconThemeSelectorDelegate>>,
|
picker: Entity<Picker<IconThemeSelectorDelegate>>,
|
||||||
|
@ -301,7 +301,12 @@ impl PickerDelegate for IconThemeSelectorDelegate {
|
||||||
.child(
|
.child(
|
||||||
Button::new("more-icon-themes", "Install Icon Themes").on_click(
|
Button::new("more-icon-themes", "Install Icon Themes").on_click(
|
||||||
move |_event, window, cx| {
|
move |_event, window, cx| {
|
||||||
window.dispatch_action(Box::new(Extensions), cx);
|
window.dispatch_action(
|
||||||
|
Box::new(Extensions {
|
||||||
|
category_filter: Some(ExtensionCategoryFilter::IconThemes),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,7 @@ use theme::{Appearance, Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
|
||||||
use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
|
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};
|
||||||
use zed_actions::Extensions;
|
use zed_actions::{ExtensionCategoryFilter, Extensions};
|
||||||
|
|
||||||
use crate::icon_theme_selector::{IconThemeSelector, IconThemeSelectorDelegate};
|
use crate::icon_theme_selector::{IconThemeSelector, IconThemeSelectorDelegate};
|
||||||
|
|
||||||
|
@ -349,7 +349,12 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||||
.child(
|
.child(
|
||||||
Button::new("more-themes", "Install Themes").on_click(cx.listener({
|
Button::new("more-themes", "Install Themes").on_click(cx.listener({
|
||||||
move |_, _, window, cx| {
|
move |_, _, window, cx| {
|
||||||
window.dispatch_action(Box::new(Extensions), cx);
|
window.dispatch_action(
|
||||||
|
Box::new(Extensions {
|
||||||
|
category_filter: Some(ExtensionCategoryFilter::Themes),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
|
@ -683,7 +683,10 @@ impl TitleBar {
|
||||||
"Icon Themes…",
|
"Icon Themes…",
|
||||||
zed_actions::icon_theme_selector::Toggle::default().boxed_clone(),
|
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()
|
.separator()
|
||||||
.link(
|
.link(
|
||||||
"Book Onboarding",
|
"Book Onboarding",
|
||||||
|
@ -730,7 +733,10 @@ impl TitleBar {
|
||||||
"Icon Themes…",
|
"Icon Themes…",
|
||||||
zed_actions::icon_theme_selector::Toggle::default().boxed_clone(),
|
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()
|
.separator()
|
||||||
.link(
|
.link(
|
||||||
"Book Onboarding",
|
"Book Onboarding",
|
||||||
|
|
|
@ -248,7 +248,7 @@ impl Render for WelcomePage {
|
||||||
.on_click(cx.listener(|_, _, window, cx| {
|
.on_click(cx.listener(|_, _, window, cx| {
|
||||||
telemetry::event!("Welcome Extensions Page Opened");
|
telemetry::event!("Welcome Extensions Page Opened");
|
||||||
window.dispatch_action(Box::new(
|
window.dispatch_action(Box::new(
|
||||||
zed_actions::Extensions,
|
zed_actions::Extensions::default(),
|
||||||
), cx);
|
), cx);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub fn app_menus() -> Vec<Menu> {
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}),
|
}),
|
||||||
MenuItem::separator(),
|
MenuItem::separator(),
|
||||||
MenuItem::action("Extensions", zed_actions::Extensions),
|
MenuItem::action("Extensions", zed_actions::Extensions::default()),
|
||||||
MenuItem::action("Install CLI", install_cli::Install),
|
MenuItem::action("Install CLI", install_cli::Install),
|
||||||
MenuItem::separator(),
|
MenuItem::separator(),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|
|
@ -35,12 +35,32 @@ actions!(
|
||||||
Quit,
|
Quit,
|
||||||
OpenKeymap,
|
OpenKeymap,
|
||||||
About,
|
About,
|
||||||
Extensions,
|
|
||||||
OpenLicenses,
|
OpenLicenses,
|
||||||
OpenTelemetryLog,
|
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<ExtensionCategoryFilter>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||||
pub struct DecreaseBufferFontSize {
|
pub struct DecreaseBufferFontSize {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -80,6 +100,7 @@ pub struct ResetUiFontSize {
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
zed,
|
zed,
|
||||||
[
|
[
|
||||||
|
Extensions,
|
||||||
DecreaseBufferFontSize,
|
DecreaseBufferFontSize,
|
||||||
IncreaseBufferFontSize,
|
IncreaseBufferFontSize,
|
||||||
ResetBufferFontSize,
|
ResetBufferFontSize,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue