mod components;mod extension_suggest;mod extension_version_selector; use std::sync::OnceLock;use std::time::Duration;use std::{ops::Range, sync::Arc}; use anyhow::Context as _;use client::zed_urls;use cloud_api_types::{ExtensionMetadata, ExtensionProvides};use collections::{BTreeMap, BTreeSet};use editor::{Editor, EditorElement, EditorStyle};use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};use fuzzy::{StringMatchCandidate, match_strings};use gpui::{ Action, App, ClipboardItem, Context, Corner, Entity, EventEmitter, Focusable, InteractiveElement, KeyContext, ParentElement, Point, Render, Styled, Task, TextStyle, UniformListScrollHandle, WeakEntity, Window, actions, point, uniform_list,};use num_format::{Locale, ToFormattedString};use project::DirectoryLister;use release_channel::ReleaseChannel;use settings::{Settings, SettingsContent};use strum::IntoEnumIterator as _;use theme::ThemeSettings;use ui::{ Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButtonGroup, ToggleButtonGroupSize, ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip, WithScrollbar, prelude::*,};use vim_mode_setting::VimModeSetting;use workspace::{ Workspace, item::{Item, ItemEvent},};use zed_actions::ExtensionCategoryFilter; use crate::components::ExtensionCard;use crate::extension_version_selector::{ ExtensionVersionSelector, ExtensionVersionSelectorDelegate,}; actions!( zed, [ /// Installs an extension from a local directory for development. InstallDevExtension ]); pub fn init(cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; workspace .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::AgentServers => ExtensionProvides::AgentServers, ExtensionCategoryFilter::SlashCommands => ExtensionProvides::SlashCommands, ExtensionCategoryFilter::IndexedDocsProviders => { ExtensionProvides::IndexedDocsProviders } ExtensionCategoryFilter::Snippets => ExtensionProvides::Snippets, ExtensionCategoryFilter::DebugAdapters => ExtensionProvides::DebugAdapters, }); let existing = workspace .active_pane() .read(cx) .items() .find_map(|item| item.downcast::<ExtensionsPage>()); if let Some(existing) = existing { existing.update(cx, |extensions_page, cx| { if provides_filter.is_some() { extensions_page.change_provides_filter(provides_filter, cx); } if let Some(id) = action.id.as_ref() { extensions_page.focus_extension(id, window, cx); } }); workspace.activate_item(&existing, true, true, window, cx); } else { let extensions_page = ExtensionsPage::new( workspace, provides_filter, action.id.as_deref(), 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( gpui::PathPromptOptions { files: false, directories: true, multiple: false, prompt: None, }, DirectoryLister::Local( workspace.project().clone(), workspace.app_state().fs.clone(), ), window, cx, ); let workspace_handle = cx.entity().downgrade(); window .spawn(cx, async move |cx| { let extension_path = match prompt.await.map_err(anyhow::Error::from) { Ok(Some(mut paths)) => paths.pop()?, Ok(None) => return None, Err(err) => { workspace_handle .update(cx, |workspace, cx| { workspace.show_portal_error(err.to_string(), cx); }) .ok(); return None; } }; let install_task = store.update(cx, |store, cx| { store.install_dev_extension(extension_path, cx) }); match install_task.await { Ok(_) => {} Err(err) => { log::error!("Failed to install dev extension: {:?}", err); workspace_handle .update(cx, |workspace, cx| { workspace.show_error( // NOTE: using `anyhow::context` here ends up not printing // the error &format!("Failed to install dev extension: {}", err), cx, ); }) .ok(); } } Some(()) }) .detach(); }); cx.subscribe_in(workspace.project(), window, |_, _, event, window, cx| { if let project::Event::LanguageNotFound(buffer) = event { extension_suggest::suggest(buffer.clone(), window, cx); } }) .detach(); }) .detach();} fn extension_provides_label(provides: ExtensionProvides) -> &'static str { match provides { ExtensionProvides::Themes => "Themes", ExtensionProvides::IconThemes => "Icon Themes", ExtensionProvides::Languages => "Languages", ExtensionProvides::Grammars => "Grammars", ExtensionProvides::LanguageServers => "Language Servers", ExtensionProvides::ContextServers => "MCP Servers", ExtensionProvides::AgentServers => "Agent Servers", ExtensionProvides::SlashCommands => "Slash Commands", ExtensionProvides::IndexedDocsProviders => "Indexed Docs Providers", ExtensionProvides::Snippets => "Snippets", ExtensionProvides::DebugAdapters => "Debug Adapters", }} #[derive(Clone)]pub enum ExtensionStatus { NotInstalled, Installing, Upgrading, Installed(Arc<str>), Removing,} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]enum ExtensionFilter { All, Installed, NotInstalled,} impl ExtensionFilter { pub fn include_dev_extensions(&self) -> bool { match self { Self::All | Self::Installed => true, Self::NotInstalled => false, } }} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]enum Feature { AgentClaude, AgentCodex, AgentGemini, ExtensionBasedpyright, ExtensionRuff, ExtensionTailwind, ExtensionTy, Git, LanguageBash, LanguageC, LanguageCpp, LanguageGo, LanguagePython, LanguageReact, LanguageRust, LanguageTypescript, OpenIn, Vim,} fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> { static KEYWORDS_BY_FEATURE: OnceLock<BTreeMap<Feature, Vec<&'static str>>> = OnceLock::new(); KEYWORDS_BY_FEATURE.get_or_init(|| { BTreeMap::from_iter([ ( Feature::AgentClaude, vec!["claude", "claude code", "claude agent"], ), (Feature::AgentCodex, vec!["codex", "codex cli"]), (Feature::AgentGemini, vec!["gemini", "gemini cli"]), ( Feature::ExtensionBasedpyright, vec!["basedpyright", "pyright"], ), (Feature::ExtensionRuff, vec!["ruff"]), (Feature::ExtensionTailwind, vec!["tail", "tailwind"]), (Feature::ExtensionTy, vec!["ty"]), (Feature::Git, vec!["git"]), (Feature::LanguageBash, vec!["sh", "bash"]), (Feature::LanguageC, vec!["c", "clang"]), (Feature::LanguageCpp, vec!["c++", "cpp", "clang"]), (Feature::LanguageGo, vec!["go", "golang"]), (Feature::LanguagePython, vec!["python", "py"]), (Feature::LanguageReact, vec!["react"]), (Feature::LanguageRust, vec!["rust", "rs"]), ( Feature::LanguageTypescript, vec!["type", "typescript", "ts"], ), ( Feature::OpenIn, vec![ "github", "gitlab", "bitbucket", "codeberg", "sourcehut", "permalink", "link", "open in", ], ), (Feature::Vim, vec!["vim"]), ]) })} fn acp_registry_upsell_keywords() -> &'static [&'static str] { &[ "opencode", "mistral", "auggie", "stakpak", "codebuddy", "autohand", "factory droid", "corust", ]} fn extension_button_id(extension_id: &Arc<str>, operation: ExtensionOperation) -> ElementId { (SharedString::from(extension_id.clone()), operation as usize).into()} struct ExtensionCardButtons { install_or_uninstall: Button, upgrade: Option<Button>, configure: Option<Button>,} pub struct ExtensionsPage { workspace: WeakEntity<Workspace>, list: UniformListScrollHandle, is_fetching_extensions: bool, fetch_failed: bool, filter: ExtensionFilter, remote_extension_entries: Vec<ExtensionMetadata>, dev_extension_entries: Vec<Arc<ExtensionManifest>>, filtered_remote_extension_indices: Vec<usize>, filtered_dev_extension_indices: Vec<usize>, query_editor: Entity<Editor>, query_contains_error: bool, provides_filter: Option<ExtensionProvides>, _subscriptions: [gpui::Subscription; 2], extension_fetch_task: Option<Task<()>>, upsells: BTreeSet<Feature>, show_acp_registry_upsell: bool,} impl ExtensionsPage { pub fn new( workspace: &Workspace, provides_filter: Option<ExtensionProvides>, focus_extension_id: Option<&str>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Entity<Self> { cx.new(|cx| { let store = ExtensionStore::global(cx); let workspace_handle = workspace.weak_handle(); let subscriptions = [ cx.observe(&store, |_: &mut Self, _, cx| cx.notify()), cx.subscribe_in( &store, window, move |this, _, event, window, cx| match event { extension_host::Event::ExtensionsUpdated => { this.fetch_extensions_debounced(None, cx) } extension_host::Event::ExtensionInstalled(extension_id) => this .on_extension_installed( workspace_handle.clone(), extension_id, window, cx, ), _ => {} }, ), ]; let query_editor = cx.new(|cx| { let mut input = Editor::single_line(window, cx); input.set_placeholder_text("Search extensions...", window, cx); if let Some(id) = focus_extension_id { input.set_text(format!("id:{id}"), window, cx); } input }); cx.subscribe(&query_editor, Self::on_query_change).detach(); let scroll_handle = UniformListScrollHandle::new(); let mut this = Self { workspace: workspace.weak_handle(), list: scroll_handle, is_fetching_extensions: false, fetch_failed: false, filter: ExtensionFilter::All, dev_extension_entries: Vec::new(), filtered_remote_extension_indices: Vec::new(), filtered_dev_extension_indices: Vec::new(), remote_extension_entries: Vec::new(), query_contains_error: false, provides_filter, extension_fetch_task: None, _subscriptions: subscriptions, query_editor, upsells: BTreeSet::default(), show_acp_registry_upsell: false, }; this.fetch_extensions( this.search_query(cx), Some(BTreeSet::from_iter(this.provides_filter)), None, cx, ); this }) } fn on_extension_installed( &mut self, workspace: WeakEntity<Workspace>, extension_id: &str, window: &mut Window, cx: &mut Context<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| { window.dispatch_action( zed_actions::theme_selector::Toggle { themes_filter: Some(themes), } .boxed_clone(), cx, ); }) .ok(); return; } let icon_themes = extension_store .extension_icon_themes(extension_id) .map(|name| name.to_string()) .collect::<Vec<_>>(); if !icon_themes.is_empty() { workspace .update(cx, |_workspace, cx| { window.dispatch_action( zed_actions::icon_theme_selector::Toggle { themes_filter: Some(icon_themes), } .boxed_clone(), cx, ); }) .ok(); } } /// Returns whether a dev extension currently exists for the extension with the given ID. fn dev_extension_exists(extension_id: &str, cx: &mut Context<Self>) -> bool { let extension_store = ExtensionStore::global(cx).read(cx); extension_store .dev_extensions() .any(|dev_extension| dev_extension.id.as_ref() == extension_id) } fn extension_status(extension_id: &str, cx: &mut Context<Self>) -> ExtensionStatus { let extension_store = ExtensionStore::global(cx).read(cx); match extension_store.outstanding_operations().get(extension_id) { Some(ExtensionOperation::Install) => ExtensionStatus::Installing, Some(ExtensionOperation::Remove) => ExtensionStatus::Removing, Some(ExtensionOperation::Upgrade) => ExtensionStatus::Upgrading, None => match extension_store.installed_extensions().get(extension_id) { Some(extension) => ExtensionStatus::Installed(extension.manifest.version.clone()), None => ExtensionStatus::NotInstalled, }, } } fn filter_extension_entries(&mut self, cx: &mut Context<Self>) { self.filtered_remote_extension_indices.clear(); self.filtered_remote_extension_indices.extend( self.remote_extension_entries .iter() .enumerate() .filter(|(_, extension)| match self.filter { ExtensionFilter::All => true, ExtensionFilter::Installed => { let status = Self::extension_status(&extension.id, cx); matches!(status, ExtensionStatus::Installed(_)) } ExtensionFilter::NotInstalled => { let status = Self::extension_status(&extension.id, cx); matches!(status, ExtensionStatus::NotInstalled) } }) .filter(|(_, extension)| match self.provides_filter { Some(provides) => extension.manifest.provides.contains(&provides), None => true, }) .map(|(ix, _)| ix), );Lines 1–500 of 1894. Load more
mod components; mod extension_suggest; mod extension_version_selector; use std::sync::OnceLock; use std::time::Duration; use std::{ops::Range, sync::Arc}; use anyhow::Context as _; use client::zed_urls; use cloud_api_types::{ExtensionMetadata, ExtensionProvides}; use collections::{BTreeMap, BTreeSet}; use editor::{Editor, EditorElement, EditorStyle}; use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore}; use fuzzy::{StringMatchCandidate, match_strings}; use gpui::{ Action, App, ClipboardItem, Context, Corner, Entity, EventEmitter, Focusable, InteractiveElement, KeyContext, ParentElement, Point, Render, Styled, Task, TextStyle, UniformListScrollHandle, WeakEntity, Window, actions, point, uniform_list, }; use num_format::{Locale, ToFormattedString}; use project::DirectoryLister; use release_channel::ReleaseChannel; use settings::{Settings, SettingsContent}; use strum::IntoEnumIterator as _; use theme::ThemeSettings; use ui::{ Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButtonGroup, ToggleButtonGroupSize, ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip, WithScrollbar, prelude::*, }; use vim_mode_setting::VimModeSetting; use workspace::{ Workspace, item::{Item, ItemEvent}, }; use zed_actions::ExtensionCategoryFilter; use crate::components::ExtensionCard; use crate::extension_version_selector::{ ExtensionVersionSelector, ExtensionVersionSelectorDelegate, }; actions!( zed, [ /// Installs an extension from a local directory for development. InstallDevExtension ] ); pub fn init(cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; workspace .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::AgentServers => ExtensionProvides::AgentServers, ExtensionCategoryFilter::SlashCommands => ExtensionProvides::SlashCommands, ExtensionCategoryFilter::IndexedDocsProviders => { ExtensionProvides::IndexedDocsProviders } ExtensionCategoryFilter::Snippets => ExtensionProvides::Snippets, ExtensionCategoryFilter::DebugAdapters => ExtensionProvides::DebugAdapters, }); let existing = workspace .active_pane() .read(cx) .items() .find_map(|item| item.downcast::<ExtensionsPage>()); if let Some(existing) = existing { existing.update(cx, |extensions_page, cx| { if provides_filter.is_some() { extensions_page.change_provides_filter(provides_filter, cx); } if let Some(id) = action.id.as_ref() { extensions_page.focus_extension(id, window, cx); } }); workspace.activate_item(&existing, true, true, window, cx); } else { let extensions_page = ExtensionsPage::new( workspace, provides_filter, action.id.as_deref(), 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( gpui::PathPromptOptions { files: false, directories: true, multiple: false, prompt: None, }, DirectoryLister::Local( workspace.project().clone(), workspace.app_state().fs.clone(), ), window, cx, ); let workspace_handle = cx.entity().downgrade(); window .spawn(cx, async move |cx| { let extension_path = match prompt.await.map_err(anyhow::Error::from) { Ok(Some(mut paths)) => paths.pop()?, Ok(None) => return None, Err(err) => { workspace_handle .update(cx, |workspace, cx| { workspace.show_portal_error(err.to_string(), cx); }) .ok(); return None; } }; let install_task = store.update(cx, |store, cx| { store.install_dev_extension(extension_path, cx) }); match install_task.await { Ok(_) => {} Err(err) => { log::error!("Failed to install dev extension: {:?}", err); workspace_handle .update(cx, |workspace, cx| { workspace.show_error( // NOTE: using `anyhow::context` here ends up not printing // the error &format!("Failed to install dev extension: {}", err), cx, ); }) .ok(); } } Some(()) }) .detach(); }); cx.subscribe_in(workspace.project(), window, |_, _, event, window, cx| { if let project::Event::LanguageNotFound(buffer) = event { extension_suggest::suggest(buffer.clone(), window, cx); } }) .detach(); }) .detach(); } fn extension_provides_label(provides: ExtensionProvides) -> &'static str { match provides { ExtensionProvides::Themes => "Themes", ExtensionProvides::IconThemes => "Icon Themes", ExtensionProvides::Languages => "Languages", ExtensionProvides::Grammars => "Grammars", ExtensionProvides::LanguageServers => "Language Servers", ExtensionProvides::ContextServers => "MCP Servers", ExtensionProvides::AgentServers => "Agent Servers", ExtensionProvides::SlashCommands => "Slash Commands", ExtensionProvides::IndexedDocsProviders => "Indexed Docs Providers", ExtensionProvides::Snippets => "Snippets", ExtensionProvides::DebugAdapters => "Debug Adapters", } } #[derive(Clone)] pub enum ExtensionStatus { NotInstalled, Installing, Upgrading, Installed(Arc<str>), Removing, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] enum ExtensionFilter { All, Installed, NotInstalled, } impl ExtensionFilter { pub fn include_dev_extensions(&self) -> bool { match self { Self::All | Self::Installed => true, Self::NotInstalled => false, } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] enum Feature { AgentClaude, AgentCodex, AgentGemini, ExtensionBasedpyright, ExtensionRuff, ExtensionTailwind, ExtensionTy, Git, LanguageBash, LanguageC, LanguageCpp, LanguageGo, LanguagePython, LanguageReact, LanguageRust, LanguageTypescript, OpenIn, Vim, } fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> { static KEYWORDS_BY_FEATURE: OnceLock<BTreeMap<Feature, Vec<&'static str>>> = OnceLock::new(); KEYWORDS_BY_FEATURE.get_or_init(|| { BTreeMap::from_iter([ ( Feature::AgentClaude, vec!["claude", "claude code", "claude agent"], ), (Feature::AgentCodex, vec!["codex", "codex cli"]), (Feature::AgentGemini, vec!["gemini", "gemini cli"]), ( Feature::ExtensionBasedpyright, vec!["basedpyright", "pyright"], ), (Feature::ExtensionRuff, vec!["ruff"]), (Feature::ExtensionTailwind, vec!["tail", "tailwind"]), (Feature::ExtensionTy, vec!["ty"]), (Feature::Git, vec!["git"]), (Feature::LanguageBash, vec!["sh", "bash"]), (Feature::LanguageC, vec!["c", "clang"]), (Feature::LanguageCpp, vec!["c++", "cpp", "clang"]), (Feature::LanguageGo, vec!["go", "golang"]), (Feature::LanguagePython, vec!["python", "py"]), (Feature::LanguageReact, vec!["react"]), (Feature::LanguageRust, vec!["rust", "rs"]), ( Feature::LanguageTypescript, vec!["type", "typescript", "ts"], ), ( Feature::OpenIn, vec![ "github", "gitlab", "bitbucket", "codeberg", "sourcehut", "permalink", "link", "open in", ], ), (Feature::Vim, vec!["vim"]), ]) }) } fn acp_registry_upsell_keywords() -> &'static [&'static str] { &[ "opencode", "mistral", "auggie", "stakpak", "codebuddy", "autohand", "factory droid", "corust", ] } fn extension_button_id(extension_id: &Arc<str>, operation: ExtensionOperation) -> ElementId { (SharedString::from(extension_id.clone()), operation as usize).into() } struct ExtensionCardButtons { install_or_uninstall: Button, upgrade: Option<Button>, configure: Option<Button>, } pub struct ExtensionsPage { workspace: WeakEntity<Workspace>, list: UniformListScrollHandle, is_fetching_extensions: bool, fetch_failed: bool, filter: ExtensionFilter, remote_extension_entries: Vec<ExtensionMetadata>, dev_extension_entries: Vec<Arc<ExtensionManifest>>, filtered_remote_extension_indices: Vec<usize>, filtered_dev_extension_indices: Vec<usize>, query_editor: Entity<Editor>, query_contains_error: bool, provides_filter: Option<ExtensionProvides>, _subscriptions: [gpui::Subscription; 2], extension_fetch_task: Option<Task<()>>, upsells: BTreeSet<Feature>, show_acp_registry_upsell: bool, } impl ExtensionsPage { pub fn new( workspace: &Workspace, provides_filter: Option<ExtensionProvides>, focus_extension_id: Option<&str>, window: &mut Window, cx: &mut Context<Workspace>, ) -> Entity<Self> { cx.new(|cx| { let store = ExtensionStore::global(cx); let workspace_handle = workspace.weak_handle(); let subscriptions = [ cx.observe(&store, |_: &mut Self, _, cx| cx.notify()), cx.subscribe_in( &store, window, move |this, _, event, window, cx| match event { extension_host::Event::ExtensionsUpdated => { this.fetch_extensions_debounced(None, cx) } extension_host::Event::ExtensionInstalled(extension_id) => this .on_extension_installed( workspace_handle.clone(), extension_id, window, cx, ), _ => {} }, ), ]; let query_editor = cx.new(|cx| { let mut input = Editor::single_line(window, cx); input.set_placeholder_text("Search extensions...", window, cx); if let Some(id) = focus_extension_id { input.set_text(format!("id:{id}"), window, cx); } input }); cx.subscribe(&query_editor, Self::on_query_change).detach(); let scroll_handle = UniformListScrollHandle::new(); let mut this = Self { workspace: workspace.weak_handle(), list: scroll_handle, is_fetching_extensions: false, fetch_failed: false, filter: ExtensionFilter::All, dev_extension_entries: Vec::new(), filtered_remote_extension_indices: Vec::new(), filtered_dev_extension_indices: Vec::new(), remote_extension_entries: Vec::new(), query_contains_error: false, provides_filter, extension_fetch_task: None, _subscriptions: subscriptions, query_editor, upsells: BTreeSet::default(), show_acp_registry_upsell: false, }; this.fetch_extensions( this.search_query(cx), Some(BTreeSet::from_iter(this.provides_filter)), None, cx, ); this }) } fn on_extension_installed( &mut self, workspace: WeakEntity<Workspace>, extension_id: &str, window: &mut Window, cx: &mut Context<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| { window.dispatch_action( zed_actions::theme_selector::Toggle { themes_filter: Some(themes), } .boxed_clone(), cx, ); }) .ok(); return; } let icon_themes = extension_store .extension_icon_themes(extension_id) .map(|name| name.to_string()) .collect::<Vec<_>>(); if !icon_themes.is_empty() { workspace .update(cx, |_workspace, cx| { window.dispatch_action( zed_actions::icon_theme_selector::Toggle { themes_filter: Some(icon_themes), } .boxed_clone(), cx, ); }) .ok(); } } /// Returns whether a dev extension currently exists for the extension with the given ID. fn dev_extension_exists(extension_id: &str, cx: &mut Context<Self>) -> bool { let extension_store = ExtensionStore::global(cx).read(cx); extension_store .dev_extensions() .any(|dev_extension| dev_extension.id.as_ref() == extension_id) } fn extension_status(extension_id: &str, cx: &mut Context<Self>) -> ExtensionStatus { let extension_store = ExtensionStore::global(cx).read(cx); match extension_store.outstanding_operations().get(extension_id) { Some(ExtensionOperation::Install) => ExtensionStatus::Installing, Some(ExtensionOperation::Remove) => ExtensionStatus::Removing, Some(ExtensionOperation::Upgrade) => ExtensionStatus::Upgrading, None => match extension_store.installed_extensions().get(extension_id) { Some(extension) => ExtensionStatus::Installed(extension.manifest.version.clone()), None => ExtensionStatus::NotInstalled, }, } } fn filter_extension_entries(&mut self, cx: &mut Context<Self>) { self.filtered_remote_extension_indices.clear(); self.filtered_remote_extension_indices.extend( self.remote_extension_entries .iter() .enumerate() .filter(|(_, extension)| match self.filter { ExtensionFilter::All => true, ExtensionFilter::Installed => { let status = Self::extension_status(&extension.id, cx); matches!(status, ExtensionStatus::Installed(_)) } ExtensionFilter::NotInstalled => { let status = Self::extension_status(&extension.id, cx); matches!(status, ExtensionStatus::NotInstalled) } }) .filter(|(_, extension)| match self.provides_filter { Some(provides) => extension.manifest.provides.contains(&provides), None => true, }) .map(|(ix, _)| ix), );