extension: Add ExtensionEvents for listening to extension-related events (#26562)

This PR adds a new `ExtensionEvents` event bus that can be used to
listen for extension-related events throughout the app.

Today you need to have a handle to the `ExtensionStore` (which entails
depending on `extension_host`) in order to listen for extension events.

With this change subscribers only need to depend on `extension`, which
has a leaner dependency graph.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-03-12 13:01:52 -04:00 committed by GitHub
parent ffcd023f83
commit acf9b22466
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 8 deletions

1
Cargo.lock generated
View file

@ -4639,6 +4639,7 @@ dependencies = [
"collections", "collections",
"db", "db",
"editor", "editor",
"extension",
"extension_host", "extension_host",
"feature_flags", "feature_flags",
"fs", "fs",

View file

@ -1,4 +1,5 @@
pub mod extension_builder; pub mod extension_builder;
mod extension_events;
mod extension_host_proxy; mod extension_host_proxy;
mod extension_manifest; mod extension_manifest;
mod types; mod types;
@ -14,12 +15,14 @@ use gpui::{App, Task};
use language::LanguageName; use language::LanguageName;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
pub use crate::extension_events::*;
pub use crate::extension_host_proxy::*; pub use crate::extension_host_proxy::*;
pub use crate::extension_manifest::*; pub use crate::extension_manifest::*;
pub use crate::types::*; pub use crate::types::*;
/// Initializes the `extension` crate. /// Initializes the `extension` crate.
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
extension_events::init(cx);
ExtensionHostProxy::default_global(cx); ExtensionHostProxy::default_global(cx);
} }

View file

@ -0,0 +1,35 @@
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, ReadGlobal as _};
pub fn init(cx: &mut App) {
let extension_events = cx.new(ExtensionEvents::new);
cx.set_global(GlobalExtensionEvents(extension_events));
}
struct GlobalExtensionEvents(Entity<ExtensionEvents>);
impl Global for GlobalExtensionEvents {}
/// An event bus for broadcasting extension-related events throughout the app.
pub struct ExtensionEvents;
impl ExtensionEvents {
/// Returns the global [`ExtensionEvents`].
pub fn global(cx: &App) -> Entity<Self> {
GlobalExtensionEvents::global(cx).0.clone()
}
fn new(_cx: &mut Context<Self>) -> Self {
Self
}
pub fn emit(&mut self, event: Event, cx: &mut Context<Self>) {
cx.emit(event)
}
}
#[derive(Clone)]
pub enum Event {
ExtensionsUpdated,
}
impl EventEmitter<Event> for ExtensionEvents {}

View file

@ -14,7 +14,7 @@ use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet};
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
pub use extension::ExtensionManifest; pub use extension::ExtensionManifest;
use extension::{ use extension::{
ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy, ExtensionContextServerProxy, ExtensionEvents, ExtensionGrammarProxy, ExtensionHostProxy,
ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy, ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,
}; };
@ -127,7 +127,6 @@ pub enum ExtensionOperation {
#[derive(Clone)] #[derive(Clone)]
pub enum Event { pub enum Event {
ExtensionsUpdated,
StartedReloading, StartedReloading,
ExtensionInstalled(Arc<str>), ExtensionInstalled(Arc<str>),
ExtensionFailedToLoad(Arc<str>), ExtensionFailedToLoad(Arc<str>),
@ -1214,7 +1213,9 @@ impl ExtensionStore {
self.extension_index = new_index; self.extension_index = new_index;
cx.notify(); cx.notify();
cx.emit(Event::ExtensionsUpdated); ExtensionEvents::global(cx).update(cx, |this, cx| {
this.emit(extension::Event::ExtensionsUpdated, cx)
});
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
cx.background_spawn({ cx.background_spawn({

View file

@ -780,6 +780,7 @@ fn init_test(cx: &mut TestAppContext) {
let store = SettingsStore::test(cx); let store = SettingsStore::test(cx);
cx.set_global(store); cx.set_global(store);
release_channel::init(SemanticVersion::default(), cx); release_channel::init(SemanticVersion::default(), cx);
extension::init(cx);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
Project::init_settings(cx); Project::init_settings(cx);
ExtensionSettings::register(cx); ExtensionSettings::register(cx);

View file

@ -17,6 +17,7 @@ client.workspace = true
collections.workspace = true collections.workspace = true
db.workspace = true db.workspace = true
editor.workspace = true editor.workspace = true
extension.workspace = true
extension_host.workspace = true extension_host.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
fs.workspace = true fs.workspace = true

View file

@ -9,6 +9,7 @@ use std::{ops::Range, sync::Arc};
use client::{ExtensionMetadata, ExtensionProvides}; use client::{ExtensionMetadata, ExtensionProvides};
use collections::{BTreeMap, BTreeSet}; use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle}; use editor::{Editor, EditorElement, EditorStyle};
use extension::ExtensionEvents;
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore}; use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use feature_flags::FeatureFlagAppExt as _; use feature_flags::FeatureFlagAppExt as _;
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
@ -212,7 +213,7 @@ pub struct ExtensionsPage {
query_editor: Entity<Editor>, query_editor: Entity<Editor>,
query_contains_error: bool, query_contains_error: bool,
provides_filter: Option<ExtensionProvides>, provides_filter: Option<ExtensionProvides>,
_subscriptions: [gpui::Subscription; 2], _subscriptions: Vec<gpui::Subscription>,
extension_fetch_task: Option<Task<()>>, extension_fetch_task: Option<Task<()>>,
upsells: BTreeSet<Feature>, upsells: BTreeSet<Feature>,
} }
@ -226,15 +227,12 @@ impl ExtensionsPage {
cx.new(|cx| { cx.new(|cx| {
let store = ExtensionStore::global(cx); let store = ExtensionStore::global(cx);
let workspace_handle = workspace.weak_handle(); let workspace_handle = workspace.weak_handle();
let subscriptions = [ let subscriptions = vec![
cx.observe(&store, |_: &mut Self, _, cx| cx.notify()), cx.observe(&store, |_: &mut Self, _, cx| cx.notify()),
cx.subscribe_in( cx.subscribe_in(
&store, &store,
window, window,
move |this, _, event, window, cx| match event { move |this, _, event, window, cx| match event {
extension_host::Event::ExtensionsUpdated => {
this.fetch_extensions_debounced(cx)
}
extension_host::Event::ExtensionInstalled(extension_id) => this extension_host::Event::ExtensionInstalled(extension_id) => this
.on_extension_installed( .on_extension_installed(
workspace_handle.clone(), workspace_handle.clone(),
@ -245,6 +243,15 @@ impl ExtensionsPage {
_ => {} _ => {}
}, },
), ),
cx.subscribe_in(
&ExtensionEvents::global(cx),
window,
move |this, _, event, _window, cx| match event {
extension::Event::ExtensionsUpdated => {
this.fetch_extensions_debounced(cx);
}
},
),
]; ];
let query_editor = cx.new(|cx| { let query_editor = cx.new(|cx| {