From 1cfcdfa7ac06665c73f2a15bcdbfb65629563a15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 22 Nov 2024 19:02:32 -0500 Subject: [PATCH] Overhaul extension registration (#21083) This PR overhauls extension registration in order to make it more modular. The `extension` crate now contains an `ExtensionHostProxy` that can be used to register various proxies that the extension host can use to interact with the rest of the system. There are now a number of different proxy traits representing the various pieces of functionality that can be provided by an extension. The respective crates that provide this functionality can implement their corresponding proxy trait in order to register a proxy that the extension host will use to register the bits of functionality provided by the extension. Release Notes: - N/A --- Cargo.lock | 49 ++- Cargo.toml | 4 + crates/assistant/src/assistant.rs | 3 +- .../src/assistant_slash_command.rs | 1 + .../src/extension_slash_command.rs | 28 +- crates/collab/Cargo.toml | 1 + .../remote_editing_collaboration_tests.rs | 4 + crates/context_servers/Cargo.toml | 1 + crates/context_servers/src/context_servers.rs | 2 + .../src/extension_context_server.rs | 78 +++++ crates/extension/Cargo.toml | 1 + crates/extension/src/extension.rs | 9 +- crates/extension/src/extension_host_proxy.rs | 324 ++++++++++++++++++ crates/extension_host/Cargo.toml | 2 + crates/extension_host/src/extension_host.rs | 146 ++------ .../src/extension_store_test.rs | 121 +------ crates/extension_host/src/headless_host.rs | 117 ++----- crates/extension_host/src/wasm_host.rs | 12 +- .../src/wasm_host/wit/since_v0_0_1.rs | 7 +- .../src/wasm_host/wit/since_v0_1_0.rs | 7 +- .../src/wasm_host/wit/since_v0_2_0.rs | 9 +- crates/extensions_ui/Cargo.toml | 7 - .../src/extension_registration_hooks.rs | 209 ----------- crates/extensions_ui/src/extensions_ui.rs | 3 - .../src/extension_indexed_docs_provider.rs | 28 +- crates/indexed_docs/src/indexed_docs.rs | 7 + crates/indexed_docs/src/registry.rs | 2 +- crates/language_extension/Cargo.toml | 25 ++ crates/language_extension/LICENSE-GPL | 1 + .../src/extension_lsp_adapter.rs | 58 +++- .../src/language_extension.rs | 51 +++ crates/remote_server/Cargo.toml | 2 + crates/remote_server/src/headless_project.rs | 6 +- .../remote_server/src/remote_editing_tests.rs | 3 + crates/remote_server/src/unix.rs | 5 + crates/snippet_provider/Cargo.toml | 1 + .../snippet_provider/src/extension_snippet.rs | 26 ++ crates/snippet_provider/src/lib.rs | 2 + crates/theme_extension/Cargo.toml | 19 + crates/theme_extension/LICENSE-GPL | 1 + crates/theme_extension/src/theme_extension.rs | 47 +++ crates/zed/Cargo.toml | 6 +- crates/zed/src/main.rs | 26 +- 43 files changed, 872 insertions(+), 589 deletions(-) create mode 100644 crates/context_servers/src/extension_context_server.rs create mode 100644 crates/extension/src/extension_host_proxy.rs delete mode 100644 crates/extensions_ui/src/extension_registration_hooks.rs create mode 100644 crates/language_extension/Cargo.toml create mode 120000 crates/language_extension/LICENSE-GPL rename crates/{extension_host => language_extension}/src/extension_lsp_adapter.rs (93%) create mode 100644 crates/language_extension/src/language_extension.rs create mode 100644 crates/snippet_provider/src/extension_snippet.rs create mode 100644 crates/theme_extension/Cargo.toml create mode 120000 crates/theme_extension/LICENSE-GPL create mode 100644 crates/theme_extension/src/theme_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 514338c59053c5cd010de927de4e7d233c0e4f11..9881c23e84e9327fa04f27589b0bbf08d202217f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2601,6 +2601,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "envy", + "extension", "file_finder", "fs", "futures 0.3.31", @@ -2842,6 +2843,7 @@ dependencies = [ "anyhow", "collections", "command_palette_hooks", + "extension", "futures 0.3.31", "gpui", "log", @@ -4127,6 +4129,7 @@ dependencies = [ "language", "log", "lsp", + "parking_lot", "semantic_version", "serde", "serde_json", @@ -4178,6 +4181,7 @@ dependencies = [ "gpui", "http_client", "language", + "language_extension", "log", "lsp", "node_runtime", @@ -4196,6 +4200,7 @@ dependencies = [ "task", "tempfile", "theme", + "theme_extension", "toml 0.8.19", "url", "util", @@ -4209,21 +4214,15 @@ name = "extensions_ui" version = "0.1.0" dependencies = [ "anyhow", - "assistant_slash_command", "client", "collections", - "context_servers", "db", "editor", - "extension", "extension_host", "fs", "fuzzy", "gpui", - "indexed_docs", "language", - "log", - "lsp", "num-format", "picker", "project", @@ -4232,7 +4231,6 @@ dependencies = [ "serde", "settings", "smallvec", - "snippet_provider", "theme", "ui", "util", @@ -6533,6 +6531,23 @@ dependencies = [ "util", ] +[[package]] +name = "language_extension" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "collections", + "extension", + "futures 0.3.31", + "gpui", + "language", + "lsp", + "serde", + "serde_json", + "util", +] + [[package]] name = "language_model" version = "0.1.0" @@ -9853,6 +9868,7 @@ dependencies = [ "client", "clock", "env_logger 0.11.5", + "extension", "extension_host", "fork", "fs", @@ -9862,6 +9878,7 @@ dependencies = [ "gpui", "http_client", "language", + "language_extension", "languages", "libc", "log", @@ -11304,6 +11321,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", + "extension", "fs", "futures 0.3.31", "gpui", @@ -12357,6 +12375,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "theme_extension" +version = "0.1.0" +dependencies = [ + "anyhow", + "extension", + "fs", + "gpui", + "theme", +] + [[package]] name = "theme_importer" version = "0.1.0" @@ -15466,7 +15495,6 @@ dependencies = [ "ashpd", "assets", "assistant", - "assistant_slash_command", "async-watch", "audio", "auto_update", @@ -15483,12 +15511,12 @@ dependencies = [ "collections", "command_palette", "command_palette_hooks", - "context_servers", "copilot", "db", "diagnostics", "editor", "env_logger 0.11.5", + "extension", "extension_host", "extensions_ui", "feature_flags", @@ -15503,11 +15531,11 @@ dependencies = [ "gpui", "http_client", "image_viewer", - "indexed_docs", "inline_completion_button", "install_cli", "journal", "language", + "language_extension", "language_model", "language_models", "language_selector", @@ -15556,6 +15584,7 @@ dependencies = [ "telemetry_events", "terminal_view", "theme", + "theme_extension", "theme_selector", "time", "toolchain_selector", diff --git a/Cargo.toml b/Cargo.toml index c12079a26acc2c5a0f97f8ee914ea30037b63048..b071ca19d118dfc96136a81f69829499e6f79e13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/install_cli", "crates/journal", "crates/language", + "crates/language_extension", "crates/language_model", "crates/language_models", "crates/language_selector", @@ -116,6 +117,7 @@ members = [ "crates/terminal_view", "crates/text", "crates/theme", + "crates/theme_extension", "crates/theme_importer", "crates/theme_selector", "crates/time_format", @@ -230,6 +232,7 @@ inline_completion_button = { path = "crates/inline_completion_button" } install_cli = { path = "crates/install_cli" } journal = { path = "crates/journal" } language = { path = "crates/language" } +language_extension = { path = "crates/language_extension" } language_model = { path = "crates/language_model" } language_models = { path = "crates/language_models" } language_selector = { path = "crates/language_selector" } @@ -292,6 +295,7 @@ terminal = { path = "crates/terminal" } terminal_view = { path = "crates/terminal_view" } text = { path = "crates/text" } theme = { path = "crates/theme" } +theme_extension = { path = "crates/theme_extension" } theme_importer = { path = "crates/theme_importer" } theme_selector = { path = "crates/theme_selector" } time_format = { path = "crates/time_format" } diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 88500247c36662d4af43ef6681cd920f1917b7ea..b891c3da2af20fc2b48fd35ac533022ebd0cd32c 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -33,7 +33,6 @@ use feature_flags::FeatureFlagAppExt; use fs::Fs; use gpui::impl_actions; use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; -use indexed_docs::IndexedDocsRegistry; pub(crate) use inline_assistant::*; use language_model::{ LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, @@ -275,7 +274,7 @@ pub fn init( client.telemetry().clone(), cx, ); - IndexedDocsRegistry::init_global(cx); + indexed_docs::init(cx); CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(Assistant::NAMESPACE); diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index 3fb2dc66b24e05005685de36bc7c2fb024b005be..59d98ee770f037e17566cd6206620a584ee74974 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace}; pub fn init(cx: &mut AppContext) { SlashCommandRegistry::default_global(cx); + extension_slash_command::init(cx); } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/assistant_slash_command/src/extension_slash_command.rs b/crates/assistant_slash_command/src/extension_slash_command.rs index bfb268806651f5e2274228087582d457fe404585..2279f93b1c942ffeaeace46e36bab173f21ccfe7 100644 --- a/crates/assistant_slash_command/src/extension_slash_command.rs +++ b/crates/assistant_slash_command/src/extension_slash_command.rs @@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc}; use anyhow::Result; use async_trait::async_trait; -use extension::{Extension, WorktreeDelegate}; -use gpui::{Task, WeakView, WindowContext}; +use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate}; +use gpui::{AppContext, Task, WeakView, WindowContext}; use language::{BufferSnapshot, LspAdapterDelegate}; use ui::prelude::*; use workspace::Workspace; use crate::{ ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, - SlashCommandResult, + SlashCommandRegistry, SlashCommandResult, }; +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_slash_command_proxy(SlashCommandRegistryProxy { + slash_command_registry: SlashCommandRegistry::global(cx), + }); +} + +struct SlashCommandRegistryProxy { + slash_command_registry: Arc, +} + +impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy { + fn register_slash_command( + &self, + extension: Arc, + command: extension::SlashCommand, + ) { + self.slash_command_registry + .register_command(ExtensionSlashCommand::new(extension, command), false) + } +} + /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. struct WorktreeDelegateAdapter(Arc); diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index a69eb53740f5be068c88f3c637c19c0948828624..d3da1c2816658eb2e62580d4b25c3dbe14690f41 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -90,6 +90,7 @@ collections = { workspace = true, features = ["test-support"] } ctor.workspace = true editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true +extension.workspace = true file_finder.workspace = true fs = { workspace = true, features = ["test-support"] } git = { workspace = true, features = ["test-support"] } diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 00f52e9972b38b60a85257d431445983ca9fd4ae..5b8d57a12a22a988318d46e2e159a8de82e5f092 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -1,6 +1,7 @@ use crate::tests::TestServer; use call::ActiveCall; use collections::HashSet; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _}; @@ -81,6 +82,7 @@ async fn test_sharing_an_ssh_remote_project( http_client: remote_http_client, node_runtime: node, languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) @@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches( http_client: remote_http_client, node_runtime: node, languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) @@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier( http_client: remote_http_client, node_runtime: NodeRuntime::unavailable(), languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) diff --git a/crates/context_servers/Cargo.toml b/crates/context_servers/Cargo.toml index de1e991887fb54094ed6695d6ceb9e73c227350a..cbd762c8c4f1036492d69af9871ce5d1304ccceb 100644 --- a/crates/context_servers/Cargo.toml +++ b/crates/context_servers/Cargo.toml @@ -15,6 +15,7 @@ path = "src/context_servers.rs" anyhow.workspace = true collections.workspace = true command_palette_hooks.workspace = true +extension.workspace = true futures.workspace = true gpui.workspace = true log.workspace = true diff --git a/crates/context_servers/src/context_servers.rs b/crates/context_servers/src/context_servers.rs index 87a98ca14ff3300192f53b58da06935ed551b7a3..e6b52aaee23a7fee30b98a21a8ac92ce60c029b6 100644 --- a/crates/context_servers/src/context_servers.rs +++ b/crates/context_servers/src/context_servers.rs @@ -1,4 +1,5 @@ pub mod client; +mod extension_context_server; pub mod manager; pub mod protocol; mod registry; @@ -19,6 +20,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers"; pub fn init(cx: &mut AppContext) { ContextServerSettings::register(cx); ContextServerFactoryRegistry::default_global(cx); + extension_context_server::init(cx); CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE); diff --git a/crates/context_servers/src/extension_context_server.rs b/crates/context_servers/src/extension_context_server.rs new file mode 100644 index 0000000000000000000000000000000000000000..092816b5e6c035dfbd722476a4f9e3d76253c019 --- /dev/null +++ b/crates/context_servers/src/extension_context_server.rs @@ -0,0 +1,78 @@ +use std::sync::Arc; + +use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate}; +use gpui::{AppContext, Model}; + +use crate::manager::ServerCommand; +use crate::ContextServerFactoryRegistry; + +struct ExtensionProject { + worktree_ids: Vec, +} + +impl ProjectDelegate for ExtensionProject { + fn worktree_ids(&self) -> Vec { + self.worktree_ids.clone() + } +} + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy { + context_server_factory_registry: ContextServerFactoryRegistry::global(cx), + }); +} + +struct ContextServerFactoryRegistryProxy { + context_server_factory_registry: Model, +} + +impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy { + fn register_context_server( + &self, + extension: Arc, + id: Arc, + cx: &mut AppContext, + ) { + self.context_server_factory_registry + .update(cx, |registry, _| { + registry.register_server_factory( + id.clone(), + Arc::new({ + move |project, cx| { + log::info!( + "loading command for context server {id} from extension {}", + extension.manifest().id + ); + + let id = id.clone(); + let extension = extension.clone(); + cx.spawn(|mut cx| async move { + let extension_project = + project.update(&mut cx, |project, cx| { + Arc::new(ExtensionProject { + worktree_ids: project + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).id().to_proto()) + .collect(), + }) + })?; + + let command = extension + .context_server_command(id.clone(), extension_project) + .await?; + + log::info!("loaded command for context server {id}: {command:?}"); + + Ok(ServerCommand { + path: command.command, + args: command.args, + env: Some(command.env.into_iter().collect()), + }) + }) + } + }), + ) + }); + } +} diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index a96cf7155a9ad522029cade1d7747ff9af13b696..b92771d09daa7fd34568338d9971b5b4e7fd9cf1 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -24,6 +24,7 @@ http_client.workspace = true language.workspace = true log.workspace = true lsp.workspace = true +parking_lot.workspace = true semantic_version.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index fe9b49909b9dd4540443adfc082940cb5830c5c7..2eb067ca40a69c5c1e8e8e951c2f06601c9ab236 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -1,4 +1,5 @@ pub mod extension_builder; +mod extension_host_proxy; mod extension_manifest; mod types; @@ -9,13 +10,19 @@ use ::lsp::LanguageServerName; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use fs::normalize_path; -use gpui::Task; +use gpui::{AppContext, Task}; use language::LanguageName; use semantic_version::SemanticVersion; +pub use crate::extension_host_proxy::*; pub use crate::extension_manifest::*; pub use crate::types::*; +/// Initializes the `extension` crate. +pub fn init(cx: &mut AppContext) { + ExtensionHostProxy::default_global(cx); +} + #[async_trait] pub trait WorktreeDelegate: Send + Sync + 'static { fn id(&self) -> u64; diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs new file mode 100644 index 0000000000000000000000000000000000000000..8909a6082dee9cca9ab79582c3ec1983a0105717 --- /dev/null +++ b/crates/extension/src/extension_host_proxy.rs @@ -0,0 +1,324 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use fs::Fs; +use gpui::{AppContext, Global, ReadGlobal, SharedString, Task}; +use language::{LanguageMatcher, LanguageName, LanguageServerBinaryStatus, LoadedLanguage}; +use lsp::LanguageServerName; +use parking_lot::RwLock; + +use crate::{Extension, SlashCommand}; + +#[derive(Default)] +struct GlobalExtensionHostProxy(Arc); + +impl Global for GlobalExtensionHostProxy {} + +/// A proxy for interacting with the extension host. +/// +/// This object implements each of the individual proxy types so that their +/// methods can be called directly on it. +#[derive(Default)] +pub struct ExtensionHostProxy { + theme_proxy: RwLock>>, + grammar_proxy: RwLock>>, + language_proxy: RwLock>>, + language_server_proxy: RwLock>>, + snippet_proxy: RwLock>>, + slash_command_proxy: RwLock>>, + context_server_proxy: RwLock>>, + indexed_docs_provider_proxy: RwLock>>, +} + +impl ExtensionHostProxy { + /// Returns the global [`ExtensionHostProxy`]. + pub fn global(cx: &AppContext) -> Arc { + GlobalExtensionHostProxy::global(cx).0.clone() + } + + /// Returns the global [`ExtensionHostProxy`]. + /// + /// Inserts a default [`ExtensionHostProxy`] if one does not yet exist. + pub fn default_global(cx: &mut AppContext) -> Arc { + cx.default_global::().0.clone() + } + + pub fn new() -> Self { + Self { + theme_proxy: RwLock::default(), + grammar_proxy: RwLock::default(), + language_proxy: RwLock::default(), + language_server_proxy: RwLock::default(), + snippet_proxy: RwLock::default(), + slash_command_proxy: RwLock::default(), + context_server_proxy: RwLock::default(), + indexed_docs_provider_proxy: RwLock::default(), + } + } + + pub fn register_theme_proxy(&self, proxy: impl ExtensionThemeProxy) { + self.theme_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_grammar_proxy(&self, proxy: impl ExtensionGrammarProxy) { + self.grammar_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_language_proxy(&self, proxy: impl ExtensionLanguageProxy) { + self.language_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_language_server_proxy(&self, proxy: impl ExtensionLanguageServerProxy) { + self.language_server_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_snippet_proxy(&self, proxy: impl ExtensionSnippetProxy) { + self.snippet_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_slash_command_proxy(&self, proxy: impl ExtensionSlashCommandProxy) { + self.slash_command_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_context_server_proxy(&self, proxy: impl ExtensionContextServerProxy) { + self.context_server_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_indexed_docs_provider_proxy( + &self, + proxy: impl ExtensionIndexedDocsProviderProxy, + ) { + self.indexed_docs_provider_proxy + .write() + .replace(Arc::new(proxy)); + } +} + +pub trait ExtensionThemeProxy: Send + Sync + 'static { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>>; + + fn remove_user_themes(&self, themes: Vec); + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task>; + + fn reload_current_theme(&self, cx: &mut AppContext); +} + +impl ExtensionThemeProxy for ExtensionHostProxy { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { + let Some(proxy) = self.theme_proxy.read().clone() else { + return Task::ready(Ok(Vec::new())); + }; + + proxy.list_theme_names(theme_path, fs) + } + + fn remove_user_themes(&self, themes: Vec) { + let Some(proxy) = self.theme_proxy.read().clone() else { + return; + }; + + proxy.remove_user_themes(themes) + } + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { + let Some(proxy) = self.theme_proxy.read().clone() else { + return Task::ready(Ok(())); + }; + + proxy.load_user_theme(theme_path, fs) + } + + fn reload_current_theme(&self, cx: &mut AppContext) { + let Some(proxy) = self.theme_proxy.read().clone() else { + return; + }; + + proxy.reload_current_theme(cx) + } +} + +pub trait ExtensionGrammarProxy: Send + Sync + 'static { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>); +} + +impl ExtensionGrammarProxy for ExtensionHostProxy { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { + let Some(proxy) = self.grammar_proxy.read().clone() else { + return; + }; + + proxy.register_grammars(grammars) + } +} + +pub trait ExtensionLanguageProxy: Send + Sync + 'static { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ); + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ); +} + +impl ExtensionLanguageProxy for ExtensionHostProxy { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ) { + let Some(proxy) = self.language_proxy.read().clone() else { + return; + }; + + proxy.register_language(language, grammar, matcher, load) + } + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ) { + let Some(proxy) = self.language_proxy.read().clone() else { + return; + }; + + proxy.remove_languages(languages_to_remove, grammars_to_remove) + } +} + +pub trait ExtensionLanguageServerProxy: Send + Sync + 'static { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ); + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ); + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ); +} + +impl ExtensionLanguageServerProxy for ExtensionHostProxy { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.register_language_server(extension, language_server_id, language) + } + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.remove_language_server(language, language_server_id) + } + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.update_language_server_status(language_server_id, status) + } +} + +pub trait ExtensionSnippetProxy: Send + Sync + 'static { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>; +} + +impl ExtensionSnippetProxy for ExtensionHostProxy { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { + let Some(proxy) = self.snippet_proxy.read().clone() else { + return Ok(()); + }; + + proxy.register_snippet(path, snippet_contents) + } +} + +pub trait ExtensionSlashCommandProxy: Send + Sync + 'static { + fn register_slash_command(&self, extension: Arc, command: SlashCommand); +} + +impl ExtensionSlashCommandProxy for ExtensionHostProxy { + fn register_slash_command(&self, extension: Arc, command: SlashCommand) { + let Some(proxy) = self.slash_command_proxy.read().clone() else { + return; + }; + + proxy.register_slash_command(extension, command) + } +} + +pub trait ExtensionContextServerProxy: Send + Sync + 'static { + fn register_context_server( + &self, + extension: Arc, + server_id: Arc, + cx: &mut AppContext, + ); +} + +impl ExtensionContextServerProxy for ExtensionHostProxy { + fn register_context_server( + &self, + extension: Arc, + server_id: Arc, + cx: &mut AppContext, + ) { + let Some(proxy) = self.context_server_proxy.read().clone() else { + return; + }; + + proxy.register_context_server(extension, server_id, cx) + } +} + +pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc); +} + +impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc) { + let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else { + return; + }; + + proxy.register_indexed_docs_provider(extension, provider_id) + } +} diff --git a/crates/extension_host/Cargo.toml b/crates/extension_host/Cargo.toml index 31d3df88aa1b41bcc9b816e6222c8d3fb734547a..6e78654b7e2dcf53668ffe25f73bc1467220b6b1 100644 --- a/crates/extension_host/Cargo.toml +++ b/crates/extension_host/Cargo.toml @@ -57,7 +57,9 @@ env_logger.workspace = true fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] } +language_extension.workspace = true parking_lot.workspace = true project = { workspace = true, features = ["test-support"] } reqwest_client.workspace = true theme = { workspace = true, features = ["test-support"] } +theme_extension.workspace = true diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 85da8127959e74581e53a7c86645fb894f0b8faf..aab5c258f50fda5a22524ec74041fe93485dd683 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1,4 +1,3 @@ -pub mod extension_lsp_adapter; pub mod extension_settings; pub mod headless_host; pub mod wasm_host; @@ -12,8 +11,12 @@ use async_tar::Archive; use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; use collections::{btree_map, BTreeMap, HashMap, HashSet}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; -use extension::Extension; pub use extension::ExtensionManifest; +use extension::{ + ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy, + ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, + ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy, +}; use fs::{Fs, RemoveOptions}; use futures::{ channel::{ @@ -24,15 +27,14 @@ use futures::{ select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _, }; use gpui::{ - actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, - SharedString, Task, WeakModel, + actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task, + WeakModel, }; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use language::{ LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, QUERY_FILENAME_PREFIXES, }; -use lsp::LanguageServerName; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; use release_channel::ReleaseChannel; @@ -95,82 +97,8 @@ pub fn is_version_compatible( true } -pub trait ExtensionRegistrationHooks: Send + Sync + 'static { - fn remove_user_themes(&self, _themes: Vec) {} - - fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc) -> Task> { - Task::ready(Ok(())) - } - - fn list_theme_names( - &self, - _theme_path: PathBuf, - _fs: Arc, - ) -> Task>> { - Task::ready(Ok(Vec::new())) - } - - fn reload_current_theme(&self, _cx: &mut AppContext) {} - - fn register_language( - &self, - _language: LanguageName, - _grammar: Option>, - _matcher: language::LanguageMatcher, - _load: Arc Result + 'static + Send + Sync>, - ) { - } - - fn register_lsp_adapter( - &self, - _extension: Arc, - _language_server_id: LanguageServerName, - _language: LanguageName, - ) { - } - - fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {} - - fn register_wasm_grammars(&self, _grammars: Vec<(Arc, PathBuf)>) {} - - fn remove_languages( - &self, - _languages_to_remove: &[LanguageName], - _grammars_to_remove: &[Arc], - ) { - } - - fn register_slash_command( - &self, - _extension: Arc, - _command: extension::SlashCommand, - ) { - } - - fn register_context_server( - &self, - _extension: Arc, - _id: Arc, - _cx: &mut AppContext, - ) { - } - - fn register_docs_provider(&self, _extension: Arc, _provider_id: Arc) {} - - fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> { - Ok(()) - } - - fn update_lsp_status( - &self, - _server_name: lsp::LanguageServerName, - _status: language::LanguageServerBinaryStatus, - ) { - } -} - pub struct ExtensionStore { - pub registration_hooks: Arc, + pub proxy: Arc, pub builder: Arc, pub extension_index: ExtensionIndex, pub fs: Arc, @@ -240,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry { actions!(zed, [ReloadExtensions]); pub fn init( - registration_hooks: Arc, + extension_host_proxy: Arc, fs: Arc, client: Arc, node_runtime: NodeRuntime, @@ -252,7 +180,7 @@ pub fn init( ExtensionStore::new( paths::extensions_dir().clone(), None, - registration_hooks, + extension_host_proxy, fs, client.http_client().clone(), client.http_client().clone(), @@ -284,7 +212,7 @@ impl ExtensionStore { pub fn new( extensions_dir: PathBuf, build_dir: Option, - extension_api: Arc, + extension_host_proxy: Arc, fs: Arc, http_client: Arc, builder_client: Arc, @@ -300,7 +228,7 @@ impl ExtensionStore { let (reload_tx, mut reload_rx) = unbounded(); let (connection_registered_tx, mut connection_registered_rx) = unbounded(); let mut this = Self { - registration_hooks: extension_api.clone(), + proxy: extension_host_proxy.clone(), extension_index: Default::default(), installed_dir, index_path, @@ -312,7 +240,7 @@ impl ExtensionStore { fs.clone(), http_client.clone(), node_runtime, - extension_api, + extension_host_proxy, work_dir, cx, ), @@ -1113,16 +1041,16 @@ impl ExtensionStore { grammars_to_remove.extend(extension.manifest.grammars.keys().cloned()); for (language_server_name, config) in extension.manifest.language_servers.iter() { for language in config.languages() { - self.registration_hooks - .remove_lsp_adapter(&language, language_server_name); + self.proxy + .remove_language_server(&language, language_server_name); } } } self.wasm_extensions .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id)); - self.registration_hooks.remove_user_themes(themes_to_remove); - self.registration_hooks + self.proxy.remove_user_themes(themes_to_remove); + self.proxy .remove_languages(&languages_to_remove, &grammars_to_remove); let languages_to_add = new_index @@ -1157,8 +1085,7 @@ impl ExtensionStore { })); } - self.registration_hooks - .register_wasm_grammars(grammars_to_add); + self.proxy.register_grammars(grammars_to_add); for (language_name, language) in languages_to_add { let mut language_path = self.installed_dir.clone(); @@ -1166,7 +1093,7 @@ impl ExtensionStore { Path::new(language.extension.as_ref()), language.path.as_path(), ]); - self.registration_hooks.register_language( + self.proxy.register_language( language_name.clone(), language.grammar.clone(), language.matcher.clone(), @@ -1196,7 +1123,7 @@ impl ExtensionStore { let fs = self.fs.clone(); let wasm_host = self.wasm_host.clone(); let root_dir = self.installed_dir.clone(); - let api = self.registration_hooks.clone(); + let proxy = self.proxy.clone(); let extension_entries = extensions_to_load .iter() .filter_map(|name| new_index.extensions.get(name).cloned()) @@ -1212,13 +1139,17 @@ impl ExtensionStore { let fs = fs.clone(); async move { for theme_path in themes_to_add.into_iter() { - api.load_user_theme(theme_path, fs.clone()).await.log_err(); + proxy + .load_user_theme(theme_path, fs.clone()) + .await + .log_err(); } for snippets_path in &snippets_to_add { if let Some(snippets_contents) = fs.load(snippets_path).await.log_err() { - api.register_snippets(snippets_path, &snippets_contents) + proxy + .register_snippet(snippets_path, &snippets_contents) .log_err(); } } @@ -1259,7 +1190,7 @@ impl ExtensionStore { for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { - this.registration_hooks.register_lsp_adapter( + this.proxy.register_language_server( extension.clone(), language_server_id.clone(), language.clone(), @@ -1268,7 +1199,7 @@ impl ExtensionStore { } for (slash_command_name, slash_command) in &manifest.slash_commands { - this.registration_hooks.register_slash_command( + this.proxy.register_slash_command( extension.clone(), extension::SlashCommand { name: slash_command_name.to_string(), @@ -1283,21 +1214,18 @@ impl ExtensionStore { } for (id, _context_server_entry) in &manifest.context_servers { - this.registration_hooks.register_context_server( - extension.clone(), - id.clone(), - cx, - ); + this.proxy + .register_context_server(extension.clone(), id.clone(), cx); } for (provider_id, _provider) in &manifest.indexed_docs_providers { - this.registration_hooks - .register_docs_provider(extension.clone(), provider_id.clone()); + this.proxy + .register_indexed_docs_provider(extension.clone(), provider_id.clone()); } } this.wasm_extensions.extend(wasm_extensions); - this.registration_hooks.reload_current_theme(cx); + this.proxy.reload_current_theme(cx); }) .ok(); }) @@ -1308,7 +1236,7 @@ impl ExtensionStore { let work_dir = self.wasm_host.work_dir.clone(); let extensions_dir = self.installed_dir.clone(); let index_path = self.index_path.clone(); - let extension_api = self.registration_hooks.clone(); + let proxy = self.proxy.clone(); cx.background_executor().spawn(async move { let start_time = Instant::now(); let mut index = ExtensionIndex::default(); @@ -1334,7 +1262,7 @@ impl ExtensionStore { fs.clone(), extension_dir, &mut index, - extension_api.clone(), + proxy.clone(), ) .await .log_err(); @@ -1357,7 +1285,7 @@ impl ExtensionStore { fs: Arc, extension_dir: PathBuf, index: &mut ExtensionIndex, - extension_api: Arc, + proxy: Arc, ) -> Result<()> { let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?; let extension_id = extension_manifest.id.clone(); @@ -1409,7 +1337,7 @@ impl ExtensionStore { continue; }; - let Some(theme_families) = extension_api + let Some(theme_families) = proxy .list_theme_names(theme_path.clone(), fs.clone()) .await .log_err() diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index 5d7853961732612c366cba069fef5b5443cfb6bb..1359b5b202843c781475d2b40e5407cff0c09221 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -1,20 +1,16 @@ -use crate::extension_lsp_adapter::ExtensionLspAdapter; use crate::{ Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry, ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore, GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION, }; -use anyhow::Result; use async_compression::futures::bufread::GzipEncoder; use collections::BTreeMap; -use extension::Extension; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs, RealFs}; use futures::{io::BufReader, AsyncReadExt, StreamExt}; -use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext}; +use gpui::{Context, SemanticVersion, TestAppContext}; use http_client::{FakeHttpClient, Response}; -use language::{ - LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage, -}; +use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use parking_lot::Mutex; @@ -31,91 +27,6 @@ use std::{ use theme::ThemeRegistry; use util::test::temp_tree; -use crate::ExtensionRegistrationHooks; - -struct TestExtensionRegistrationHooks { - executor: BackgroundExecutor, - language_registry: Arc, - theme_registry: Arc, -} - -impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks { - fn list_theme_names(&self, path: PathBuf, fs: Arc) -> Task>> { - self.executor.spawn(async move { - let themes = theme::read_user_theme(&path, fs).await?; - Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) - }) - } - - fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { - let theme_registry = self.theme_registry.clone(); - self.executor - .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) - } - - fn remove_user_themes(&self, themes: Vec) { - self.theme_registry.remove_user_themes(&themes); - } - - fn register_language( - &self, - language: language::LanguageName, - grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - self.language_registry - .register_language(language, grammar, matcher, load) - } - - fn remove_languages( - &self, - languages_to_remove: &[language::LanguageName], - grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(&languages_to_remove, &grammars_to_remove); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn update_lsp_status( - &self, - server_name: lsp::LanguageServerName, - status: LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status); - } - - fn remove_lsp_adapter( - &self, - language_name: &language::LanguageName, - server_name: &lsp::LanguageServerName, - ) { - self.language_registry - .remove_lsp_adapter(language_name, server_name); - } -} - #[cfg(test)] #[ctor::ctor] fn init_logger() { @@ -347,20 +258,18 @@ async fn test_extension_store(cx: &mut TestAppContext) { .collect(), }; - let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); + let proxy = Arc::new(ExtensionHostProxy::new()); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); - let registration_hooks = Arc::new(TestExtensionRegistrationHooks { - executor: cx.executor(), - language_registry: language_registry.clone(), - theme_registry: theme_registry.clone(), - }); + theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); + let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); + language_extension::init(proxy.clone(), language_registry.clone()); let node_runtime = NodeRuntime::unavailable(); let store = cx.new_model(|cx| { ExtensionStore::new( PathBuf::from("/the-extension-dir"), None, - registration_hooks.clone(), + proxy.clone(), fs.clone(), http_client.clone(), http_client.clone(), @@ -485,7 +394,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { ExtensionStore::new( PathBuf::from("/the-extension-dir"), None, - registration_hooks, + proxy, fs.clone(), http_client.clone(), http_client.clone(), @@ -568,13 +477,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await; - let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); + let proxy = Arc::new(ExtensionHostProxy::new()); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); - let registration_hooks = Arc::new(TestExtensionRegistrationHooks { - executor: cx.executor(), - language_registry: language_registry.clone(), - theme_registry: theme_registry.clone(), - }); + theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); + language_extension::init(proxy.clone(), language_registry.clone()); let node_runtime = NodeRuntime::unavailable(); let mut status_updates = language_registry.language_server_binary_statuses(); @@ -668,7 +575,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { ExtensionStore::new( extensions_dir.clone(), Some(cache_dir), - registration_hooks, + proxy, fs.clone(), extension_client.clone(), builder_client, diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 6ad8b71aa3319350254f8f6fefab202cadcae1c6..19a574b9d4aa5c8fc1acde84586125448c7933da 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -3,59 +3,57 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{anyhow, Context as _, Result}; use client::{proto, TypedEnvelope}; use collections::{HashMap, HashSet}; -use extension::{Extension, ExtensionManifest}; +use extension::{ + Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, + ExtensionManifest, +}; use fs::{Fs, RemoveOptions, RenameOptions}; use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel}; use http_client::HttpClient; -use language::{LanguageConfig, LanguageName, LanguageQueries, LanguageRegistry, LoadedLanguage}; +use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; -use crate::{ - extension_lsp_adapter::ExtensionLspAdapter, - wasm_host::{WasmExtension, WasmHost}, - ExtensionRegistrationHooks, -}; +use crate::wasm_host::{WasmExtension, WasmHost}; + +#[derive(Clone, Debug)] +pub struct ExtensionVersion { + pub id: String, + pub version: String, + pub dev: bool, +} pub struct HeadlessExtensionStore { - pub registration_hooks: Arc, pub fs: Arc, pub extension_dir: PathBuf, + pub proxy: Arc, pub wasm_host: Arc, pub loaded_extensions: HashMap, Arc>, pub loaded_languages: HashMap, Vec>, pub loaded_language_servers: HashMap, Vec<(LanguageServerName, LanguageName)>>, } -#[derive(Clone, Debug)] -pub struct ExtensionVersion { - pub id: String, - pub version: String, - pub dev: bool, -} - impl HeadlessExtensionStore { pub fn new( fs: Arc, http_client: Arc, - languages: Arc, extension_dir: PathBuf, + extension_host_proxy: Arc, node_runtime: NodeRuntime, cx: &mut AppContext, ) -> Model { - let registration_hooks = Arc::new(HeadlessRegistrationHooks::new(languages.clone())); cx.new_model(|cx| Self { - registration_hooks: registration_hooks.clone(), fs: fs.clone(), wasm_host: WasmHost::new( fs.clone(), http_client.clone(), node_runtime, - registration_hooks, + extension_host_proxy.clone(), extension_dir.join("work"), cx, ), extension_dir, + proxy: extension_host_proxy, loaded_extensions: Default::default(), loaded_languages: Default::default(), loaded_language_servers: Default::default(), @@ -154,7 +152,7 @@ impl HeadlessExtensionStore { config.grammar = None; - this.registration_hooks.register_language( + this.proxy.register_language( config.name.clone(), None, config.matcher.clone(), @@ -184,7 +182,7 @@ impl HeadlessExtensionStore { .entry(manifest.id.clone()) .or_default() .push((language_server_id.clone(), language.clone())); - this.registration_hooks.register_lsp_adapter( + this.proxy.register_language_server( wasm_extension.clone(), language_server_id.clone(), language.clone(), @@ -202,19 +200,20 @@ impl HeadlessExtensionStore { cx: &mut ModelContext, ) -> Task> { self.loaded_extensions.remove(extension_id); + let languages_to_remove = self .loaded_languages .remove(extension_id) .unwrap_or_default(); - self.registration_hooks - .remove_languages(&languages_to_remove, &[]); + self.proxy.remove_languages(&languages_to_remove, &[]); + for (language_server_name, language) in self .loaded_language_servers .remove(extension_id) .unwrap_or_default() { - self.registration_hooks - .remove_lsp_adapter(&language, &language_server_name); + self.proxy + .remove_language_server(&language, &language_server_name); } let path = self.extension_dir.join(&extension_id.to_string()); @@ -318,71 +317,3 @@ impl HeadlessExtensionStore { Ok(proto::Ack {}) } } - -struct HeadlessRegistrationHooks { - language_registry: Arc, -} - -impl HeadlessRegistrationHooks { - fn new(language_registry: Arc) -> Self { - Self { language_registry } - } -} - -impl ExtensionRegistrationHooks for HeadlessRegistrationHooks { - fn register_language( - &self, - language: LanguageName, - _grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - log::info!("registering language: {:?}", language); - self.language_registry - .register_language(language, None, matcher, load) - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - log::info!("registering lsp adapter {:?}", language); - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn remove_lsp_adapter(&self, language: &LanguageName, server_name: &LanguageServerName) { - self.language_registry - .remove_lsp_adapter(language, server_name) - } - - fn remove_languages( - &self, - languages_to_remove: &[LanguageName], - _grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(languages_to_remove, &[]) - } - - fn update_lsp_status( - &self, - server_name: LanguageServerName, - status: language::LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status) - } -} diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 01c57599a894fb90c2d5bd1e1a624adf7b2c7ba0..766ca8c0bb1e1f10f4a28b9e54787c357c25bab8 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -1,11 +1,11 @@ pub mod wit; -use crate::{ExtensionManifest, ExtensionRegistrationHooks}; +use crate::ExtensionManifest; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use extension::{ - CodeLabel, Command, Completion, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, - SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, + CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, + SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, }; use fs::{normalize_path, Fs}; use futures::future::LocalBoxFuture; @@ -40,7 +40,7 @@ pub struct WasmHost { release_channel: ReleaseChannel, http_client: Arc, node_runtime: NodeRuntime, - pub registration_hooks: Arc, + pub(crate) proxy: Arc, fs: Arc, pub work_dir: PathBuf, _main_thread_message_task: Task<()>, @@ -330,7 +330,7 @@ impl WasmHost { fs: Arc, http_client: Arc, node_runtime: NodeRuntime, - registration_hooks: Arc, + proxy: Arc, work_dir: PathBuf, cx: &mut AppContext, ) -> Arc { @@ -346,7 +346,7 @@ impl WasmHost { work_dir, http_client, node_runtime, - registration_hooks, + proxy, release_channel: ReleaseChannel::global(cx), _main_thread_message_task: task, main_thread_message_tx: tx, diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs b/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs index bd1770de38a3ee25171373f6c929dced7b3182ce..1f0891b4105f9ccf2dae45d04c6d75debfb818f0 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs @@ -3,7 +3,7 @@ use crate::wasm_host::wit::since_v0_0_4; use crate::wasm_host::WasmState; use anyhow::Result; use async_trait::async_trait; -use extension::WorktreeDelegate; +use extension::{ExtensionLanguageServerProxy, WorktreeDelegate}; use language::LanguageServerBinaryStatus; use semantic_version::SemanticVersion; use std::sync::{Arc, OnceLock}; @@ -149,8 +149,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs index 18f4bc0234cf6edd26e2bfcfcef7b241acbc3e96..c1c07a2b09be5f785a9cb0dfb8d781302716e311 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; -use extension::{KeyValueStoreDelegate, WorktreeDelegate}; +use extension::{ExtensionLanguageServerProxy, KeyValueStoreDelegate, WorktreeDelegate}; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use language::LanguageName; @@ -495,8 +495,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs index 234eec26ec5eefbc6614a61b9cd74cc56591d6df..f7e11e1032566ac8e5ebd127d59f18db6bb2ba4d 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs @@ -8,7 +8,9 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use context_servers::manager::ContextServerSettings; -use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate}; +use extension::{ + ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate, +}; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus}; @@ -682,8 +684,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index a219fe4bd4f60229f60854720723e0874fe1908e..cc6e78d6f3603339dd0d537d869c488bf1383172 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -13,21 +13,15 @@ path = "src/extensions_ui.rs" [dependencies] anyhow.workspace = true -assistant_slash_command.workspace = true client.workspace = true collections.workspace = true -context_servers.workspace = true db.workspace = true editor.workspace = true -extension.workspace = true extension_host.workspace = true fs.workspace = true fuzzy.workspace = true gpui.workspace = true -indexed_docs.workspace = true language.workspace = true -log.workspace = true -lsp.workspace = true num-format.workspace = true picker.workspace = true project.workspace = true @@ -36,7 +30,6 @@ semantic_version.workspace = true serde.workspace = true settings.workspace = true smallvec.workspace = true -snippet_provider.workspace = true theme.workspace = true ui.workspace = true util.workspace = true diff --git a/crates/extensions_ui/src/extension_registration_hooks.rs b/crates/extensions_ui/src/extension_registration_hooks.rs deleted file mode 100644 index 1b427cd187f6e10c8df3471549d42a2225376d5f..0000000000000000000000000000000000000000 --- a/crates/extensions_ui/src/extension_registration_hooks.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::{path::PathBuf, sync::Arc}; - -use anyhow::Result; -use assistant_slash_command::{ExtensionSlashCommand, SlashCommandRegistry}; -use context_servers::manager::ServerCommand; -use context_servers::ContextServerFactoryRegistry; -use extension::{Extension, ProjectDelegate}; -use extension_host::extension_lsp_adapter::ExtensionLspAdapter; -use fs::Fs; -use gpui::{AppContext, BackgroundExecutor, Model, Task}; -use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId}; -use language::{LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage}; -use lsp::LanguageServerName; -use snippet_provider::SnippetRegistry; -use theme::{ThemeRegistry, ThemeSettings}; -use ui::SharedString; - -struct ExtensionProject { - worktree_ids: Vec, -} - -impl ProjectDelegate for ExtensionProject { - fn worktree_ids(&self) -> Vec { - self.worktree_ids.clone() - } -} - -pub struct ConcreteExtensionRegistrationHooks { - slash_command_registry: Arc, - theme_registry: Arc, - indexed_docs_registry: Arc, - snippet_registry: Arc, - language_registry: Arc, - context_server_factory_registry: Model, - executor: BackgroundExecutor, -} - -impl ConcreteExtensionRegistrationHooks { - pub fn new( - theme_registry: Arc, - slash_command_registry: Arc, - indexed_docs_registry: Arc, - snippet_registry: Arc, - language_registry: Arc, - context_server_factory_registry: Model, - cx: &AppContext, - ) -> Arc { - Arc::new(Self { - theme_registry, - slash_command_registry, - indexed_docs_registry, - snippet_registry, - language_registry, - context_server_factory_registry, - executor: cx.background_executor().clone(), - }) - } -} - -impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistrationHooks { - fn remove_user_themes(&self, themes: Vec) { - self.theme_registry.remove_user_themes(&themes); - } - - fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { - let theme_registry = self.theme_registry.clone(); - self.executor - .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) - } - - fn register_slash_command( - &self, - extension: Arc, - command: extension::SlashCommand, - ) { - self.slash_command_registry - .register_command(ExtensionSlashCommand::new(extension, command), false) - } - - fn register_context_server( - &self, - extension: Arc, - id: Arc, - cx: &mut AppContext, - ) { - self.context_server_factory_registry - .update(cx, |registry, _| { - registry.register_server_factory( - id.clone(), - Arc::new({ - move |project, cx| { - log::info!( - "loading command for context server {id} from extension {}", - extension.manifest().id - ); - - let id = id.clone(); - let extension = extension.clone(); - cx.spawn(|mut cx| async move { - let extension_project = - project.update(&mut cx, |project, cx| { - Arc::new(ExtensionProject { - worktree_ids: project - .visible_worktrees(cx) - .map(|worktree| worktree.read(cx).id().to_proto()) - .collect(), - }) - })?; - - let command = extension - .context_server_command(id.clone(), extension_project) - .await?; - - log::info!("loaded command for context server {id}: {command:?}"); - - Ok(ServerCommand { - path: command.command, - args: command.args, - env: Some(command.env.into_iter().collect()), - }) - }) - } - }), - ) - }); - } - - fn register_docs_provider(&self, extension: Arc, provider_id: Arc) { - self.indexed_docs_registry - .register_provider(Box::new(ExtensionIndexedDocsProvider::new( - extension, - ProviderId(provider_id), - ))); - } - - fn register_snippets(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { - self.snippet_registry - .register_snippets(path, snippet_contents) - } - - fn update_lsp_status( - &self, - server_name: lsp::LanguageServerName, - status: LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status); - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn remove_lsp_adapter( - &self, - language_name: &language::LanguageName, - server_name: &lsp::LanguageServerName, - ) { - self.language_registry - .remove_lsp_adapter(language_name, server_name); - } - - fn remove_languages( - &self, - languages_to_remove: &[language::LanguageName], - grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(&languages_to_remove, &grammars_to_remove); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn register_language( - &self, - language: language::LanguageName, - grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - self.language_registry - .register_language(language, grammar, matcher, load) - } - - fn reload_current_theme(&self, cx: &mut AppContext) { - ThemeSettings::reload_current_theme(cx) - } - - fn list_theme_names(&self, path: PathBuf, fs: Arc) -> Task>> { - self.executor.spawn(async move { - let themes = theme::read_user_theme(&path, fs).await?; - Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) - }) - } -} diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 077d80b9b861dd15e3374662c1de1262465eb15c..eaffdafa41517d95d6918b7d447253f705b2c8c9 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1,10 +1,7 @@ mod components; -mod extension_registration_hooks; mod extension_suggest; mod extension_version_selector; -pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks; - use std::ops::DerefMut; use std::sync::OnceLock; use std::time::Duration; diff --git a/crates/indexed_docs/src/extension_indexed_docs_provider.rs b/crates/indexed_docs/src/extension_indexed_docs_provider.rs index ed006546fe587694d92eb66a12740e549dfeba2e..25b0f163570120b57af539d34b7046e41c8fcf51 100644 --- a/crates/indexed_docs/src/extension_indexed_docs_provider.rs +++ b/crates/indexed_docs/src/extension_indexed_docs_provider.rs @@ -3,9 +3,33 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; -use extension::Extension; +use extension::{Extension, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy}; +use gpui::AppContext; -use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId}; +use crate::{ + IndexedDocsDatabase, IndexedDocsProvider, IndexedDocsRegistry, PackageName, ProviderId, +}; + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_indexed_docs_provider_proxy(IndexedDocsRegistryProxy { + indexed_docs_registry: IndexedDocsRegistry::global(cx), + }); +} + +struct IndexedDocsRegistryProxy { + indexed_docs_registry: Arc, +} + +impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc) { + self.indexed_docs_registry + .register_provider(Box::new(ExtensionIndexedDocsProvider::new( + extension, + ProviderId(provider_id), + ))); + } +} pub struct ExtensionIndexedDocsProvider { extension: Arc, diff --git a/crates/indexed_docs/src/indexed_docs.rs b/crates/indexed_docs/src/indexed_docs.rs index 95e5c6233583d52ac38633e968caf5d24ba28b15..42672cd220baba9b2ba05fe8d80bb07de6718591 100644 --- a/crates/indexed_docs/src/indexed_docs.rs +++ b/crates/indexed_docs/src/indexed_docs.rs @@ -3,7 +3,14 @@ mod providers; mod registry; mod store; +use gpui::AppContext; + pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider; pub use crate::providers::rustdoc::*; pub use crate::registry::*; pub use crate::store::*; + +pub fn init(cx: &mut AppContext) { + IndexedDocsRegistry::init_global(cx); + extension_indexed_docs_provider::init(cx); +} diff --git a/crates/indexed_docs/src/registry.rs b/crates/indexed_docs/src/registry.rs index fa3425466c3bf01019e4bb215f4be6ba88cd0d2d..6332e6c4b00f2af66d64c33bfacf9fcea123af98 100644 --- a/crates/indexed_docs/src/registry.rs +++ b/crates/indexed_docs/src/registry.rs @@ -20,7 +20,7 @@ impl IndexedDocsRegistry { GlobalIndexedDocsRegistry::global(cx).0.clone() } - pub fn init_global(cx: &mut AppContext) { + pub(crate) fn init_global(cx: &mut AppContext) { GlobalIndexedDocsRegistry::set_global( cx, GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))), diff --git a/crates/language_extension/Cargo.toml b/crates/language_extension/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..3d1e4d0a64d240f336a83f334e5650287dc93874 --- /dev/null +++ b/crates/language_extension/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "language_extension" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/language_extension.rs" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +collections.workspace = true +extension.workspace = true +futures.workspace = true +gpui.workspace = true +language.workspace = true +lsp.workspace = true +serde.workspace = true +serde_json.workspace = true +util.workspace = true diff --git a/crates/language_extension/LICENSE-GPL b/crates/language_extension/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/language_extension/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/extension_host/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs similarity index 93% rename from crates/extension_host/src/extension_lsp_adapter.rs rename to crates/language_extension/src/extension_lsp_adapter.rs index 069eddba5713ec12def93b0fa3ad7117531a8ee6..eab9529fe0a4f1f3251d268d21266345c191700a 100644 --- a/crates/extension_host/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -1,22 +1,28 @@ +use std::any::Any; +use std::ops::Range; +use std::path::PathBuf; +use std::pin::Pin; +use std::sync::Arc; + use anyhow::{Context, Result}; use async_trait::async_trait; use collections::HashMap; -use extension::{Extension, WorktreeDelegate}; +use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate}; use futures::{Future, FutureExt}; use gpui::AsyncAppContext; use language::{ - CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter, - LspAdapterDelegate, + CodeLabel, HighlightId, Language, LanguageName, LanguageServerBinaryStatus, + LanguageToolchainStore, LspAdapter, LspAdapterDelegate, }; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName}; use serde::Serialize; use serde_json::Value; -use std::ops::Range; -use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc}; use util::{maybe, ResultExt}; +use crate::LanguageServerRegistryProxy; + /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. -pub struct WorktreeDelegateAdapter(pub Arc); +struct WorktreeDelegateAdapter(pub Arc); #[async_trait] impl WorktreeDelegate for WorktreeDelegateAdapter { @@ -44,14 +50,50 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { } } -pub struct ExtensionLspAdapter { +impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ) { + self.language_registry.register_lsp_adapter( + language.clone(), + Arc::new(ExtensionLspAdapter::new( + extension, + language_server_id, + language, + )), + ); + } + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ) { + self.language_registry + .remove_lsp_adapter(language, language_server_id); + } + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ) { + self.language_registry + .update_lsp_status(language_server_id, status); + } +} + +struct ExtensionLspAdapter { extension: Arc, language_server_id: LanguageServerName, language_name: LanguageName, } impl ExtensionLspAdapter { - pub fn new( + fn new( extension: Arc, language_server_id: LanguageServerName, language_name: LanguageName, diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8ffc71d7c4ab0d0f8687f4a34ce8edf7d68163e --- /dev/null +++ b/crates/language_extension/src/language_extension.rs @@ -0,0 +1,51 @@ +mod extension_lsp_adapter; + +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy}; +use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage}; + +pub fn init( + extension_host_proxy: Arc, + language_registry: Arc, +) { + let language_server_registry_proxy = LanguageServerRegistryProxy { language_registry }; + extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone()); + extension_host_proxy.register_language_proxy(language_server_registry_proxy.clone()); + extension_host_proxy.register_language_server_proxy(language_server_registry_proxy); +} + +#[derive(Clone)] +struct LanguageServerRegistryProxy { + language_registry: Arc, +} + +impl ExtensionGrammarProxy for LanguageServerRegistryProxy { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { + self.language_registry.register_wasm_grammars(grammars) + } +} + +impl ExtensionLanguageProxy for LanguageServerRegistryProxy { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ) { + self.language_registry + .register_language(language, grammar, matcher, load); + } + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ) { + self.language_registry + .remove_languages(&languages_to_remove, &grammars_to_remove); + } +} diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index d46fb8df56e83857b071c16c1130fb19b6924867..82853217dc10a9e162a1f1f035877e213ed9ecbc 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -29,6 +29,7 @@ chrono.workspace = true clap.workspace = true client.workspace = true env_logger.workspace = true +extension.workspace = true extension_host.workspace = true fs.workspace = true futures.workspace = true @@ -37,6 +38,7 @@ git_hosting_providers.workspace = true gpui.workspace = true http_client.workspace = true language.workspace = true +language_extension.workspace = true languages.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 28cd6e115c87eb30f535f02724e20df851949028..2fb8330603ffd5ff2e946e19b73432ca82489483 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use extension::ExtensionHostProxy; use extension_host::headless_host::HeadlessExtensionStore; use fs::Fs; use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel}; @@ -47,6 +48,7 @@ pub struct HeadlessAppState { pub http_client: Arc, pub node_runtime: NodeRuntime, pub languages: Arc, + pub extension_host_proxy: Arc, } impl HeadlessProject { @@ -63,9 +65,11 @@ impl HeadlessProject { http_client, node_runtime, languages, + extension_host_proxy: proxy, }: HeadlessAppState, cx: &mut ModelContext, ) -> Self { + language_extension::init(proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let worktree_store = cx.new_model(|cx| { @@ -152,8 +156,8 @@ impl HeadlessProject { let extensions = HeadlessExtensionStore::new( fs.clone(), http_client.clone(), - languages.clone(), paths::remote_extensions_dir().to_path_buf(), + proxy, node_runtime, cx, ); diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 3a9803287aa01f734d1b8d3f7d755af81d2792ad..bdb862c5afe9db92533093c41458a9f4459b14a0 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1,6 +1,7 @@ use crate::headless_project::HeadlessProject; use client::{Client, UserStore}; use clock::FakeSystemClock; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs}; use gpui::{Context, Model, SemanticVersion, TestAppContext}; use http_client::{BlockedHttpClient, FakeHttpClient}; @@ -1234,6 +1235,7 @@ pub async fn init_test( let http_client = Arc::new(BlockedHttpClient); let node_runtime = NodeRuntime::unavailable(); let languages = Arc::new(LanguageRegistry::new(cx.executor())); + let proxy = Arc::new(ExtensionHostProxy::new()); server_cx.update(HeadlessProject::init); let headless = server_cx.new_model(|cx| { client::init_settings(cx); @@ -1245,6 +1247,7 @@ pub async fn init_test( http_client, node_runtime, languages, + extension_host_proxy: proxy, }, cx, ) diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 467fd452f83ebe699311cd61588807ba90fcf5f4..18378ec8e94ef977c04b233cc7d52443e6088607 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -3,6 +3,7 @@ use crate::HeadlessProject; use anyhow::{anyhow, Context, Result}; use chrono::Utc; use client::{telemetry, ProxySettings}; +use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::channel::mpsc; use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt}; @@ -434,6 +435,9 @@ pub fn execute_run( GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx); git_hosting_providers::init(cx); + extension::init(cx); + let extension_host_proxy = ExtensionHostProxy::global(cx); + let project = cx.new_model(|cx| { let fs = Arc::new(RealFs::new(Default::default(), None)); let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx); @@ -466,6 +470,7 @@ pub fn execute_run( http_client, node_runtime, languages, + extension_host_proxy, }, cx, ) diff --git a/crates/snippet_provider/Cargo.toml b/crates/snippet_provider/Cargo.toml index 95ab19ebb6f9926847cc30ab8f29be1138ec55a1..aa4e1a5f84a9e2724c1bcfdf5613d9b66e8438e9 100644 --- a/crates/snippet_provider/Cargo.toml +++ b/crates/snippet_provider/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] anyhow.workspace = true collections.workspace = true +extension.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true diff --git a/crates/snippet_provider/src/extension_snippet.rs b/crates/snippet_provider/src/extension_snippet.rs new file mode 100644 index 0000000000000000000000000000000000000000..41a7c886e182d9f4f534731a0e346addaea298cf --- /dev/null +++ b/crates/snippet_provider/src/extension_snippet.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionHostProxy, ExtensionSnippetProxy}; +use gpui::AppContext; + +use crate::SnippetRegistry; + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_snippet_proxy(SnippetRegistryProxy { + snippet_registry: SnippetRegistry::global(cx), + }); +} + +struct SnippetRegistryProxy { + snippet_registry: Arc, +} + +impl ExtensionSnippetProxy for SnippetRegistryProxy { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { + self.snippet_registry + .register_snippets(path, snippet_contents) + } +} diff --git a/crates/snippet_provider/src/lib.rs b/crates/snippet_provider/src/lib.rs index 17d60d25a031f3d609fdf838ac8b06cb07b98aa9..34aa1ebefc16d678f26b8debbbf6250a4f0b7c32 100644 --- a/crates/snippet_provider/src/lib.rs +++ b/crates/snippet_provider/src/lib.rs @@ -1,3 +1,4 @@ +mod extension_snippet; mod format; mod registry; @@ -18,6 +19,7 @@ use util::ResultExt; pub fn init(cx: &mut AppContext) { SnippetRegistry::init_global(cx); + extension_snippet::init(cx); } // Is `None` if the snippet file is global. diff --git a/crates/theme_extension/Cargo.toml b/crates/theme_extension/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1e12f037b97b3480a7c730a1583ec39596eee1a0 --- /dev/null +++ b/crates/theme_extension/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "theme_extension" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/theme_extension.rs" + +[dependencies] +anyhow.workspace = true +extension.workspace = true +fs.workspace = true +gpui.workspace = true +theme.workspace = true diff --git a/crates/theme_extension/LICENSE-GPL b/crates/theme_extension/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/theme_extension/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/theme_extension/src/theme_extension.rs b/crates/theme_extension/src/theme_extension.rs new file mode 100644 index 0000000000000000000000000000000000000000..0266db324be085459e1f6109cee4959c0dd57aaa --- /dev/null +++ b/crates/theme_extension/src/theme_extension.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionHostProxy, ExtensionThemeProxy}; +use fs::Fs; +use gpui::{AppContext, BackgroundExecutor, SharedString, Task}; +use theme::{ThemeRegistry, ThemeSettings}; + +pub fn init( + extension_host_proxy: Arc, + theme_registry: Arc, + executor: BackgroundExecutor, +) { + extension_host_proxy.register_theme_proxy(ThemeRegistryProxy { + theme_registry, + executor, + }); +} + +struct ThemeRegistryProxy { + theme_registry: Arc, + executor: BackgroundExecutor, +} + +impl ExtensionThemeProxy for ThemeRegistryProxy { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { + self.executor.spawn(async move { + let themes = theme::read_user_theme(&theme_path, fs).await?; + Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) + }) + } + + fn remove_user_themes(&self, themes: Vec) { + self.theme_registry.remove_user_themes(&themes); + } + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { + let theme_registry = self.theme_registry.clone(); + self.executor + .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) + } + + fn reload_current_theme(&self, cx: &mut AppContext) { + ThemeSettings::reload_current_theme(cx) + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 52ec265480bbf97fdc08d7c69235ed7590fd33a6..755076e360d58ade1a6d9a48d9ed5e635d1ca9ea 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -19,7 +19,6 @@ activity_indicator.workspace = true anyhow.workspace = true assets.workspace = true assistant.workspace = true -assistant_slash_command.workspace = true async-watch.workspace = true audio.workspace = true auto_update.workspace = true @@ -36,12 +35,12 @@ collab_ui.workspace = true collections.workspace = true command_palette.workspace = true command_palette_hooks.workspace = true -context_servers.workspace = true copilot.workspace = true db.workspace = true diagnostics.workspace = true editor.workspace = true env_logger.workspace = true +extension.workspace = true extension_host.workspace = true extensions_ui.workspace = true feature_flags.workspace = true @@ -56,11 +55,11 @@ go_to_line.workspace = true gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] } http_client.workspace = true image_viewer.workspace = true -indexed_docs.workspace = true inline_completion_button.workspace = true install_cli.workspace = true journal.workspace = true language.workspace = true +language_extension.workspace = true language_model.workspace = true language_models.workspace = true language_selector.workspace = true @@ -109,6 +108,7 @@ tasks_ui.workspace = true telemetry_events.workspace = true terminal_view.workspace = true theme.workspace = true +theme_extension.workspace = true theme_selector.workspace = true time.workspace = true toolchain_selector.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e96a70f91d80d5df25988fff68def560a7a333cf..73b0e0f199e4c0138f798076423810b7c7b587d2 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,16 +5,15 @@ mod reliability; mod zed; use anyhow::{anyhow, Context as _, Result}; -use assistant_slash_command::SlashCommandRegistry; use chrono::Offset; use clap::{command, Parser}; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::{parse_zed_link, Client, ProxySettings, UserStore}; use collab_ui::channel_view::ChannelView; -use context_servers::ContextServerFactoryRegistry; use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE}; use editor::Editor; use env_logger::Builder; +use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::{future, StreamExt}; use git::GitHostingProviderRegistry; @@ -23,7 +22,6 @@ use gpui::{ VisualContext, }; use http_client::{read_proxy_from_env, Uri}; -use indexed_docs::IndexedDocsRegistry; use language::LanguageRegistry; use log::LevelFilter; use reqwest_client::ReqwestClient; @@ -40,7 +38,6 @@ use settings::{ }; use simplelog::ConfigBuilder; use smol::process::Command; -use snippet_provider::SnippetRegistry; use std::{ env, fs::OpenOptions, @@ -284,6 +281,9 @@ fn main() { OpenListener::set_global(cx, open_listener.clone()); + extension::init(cx); + let extension_host_proxy = ExtensionHostProxy::global(cx); + let client = Client::production(cx); cx.set_http_client(client.http_client().clone()); let mut languages = LanguageRegistry::new(cx.background_executor().clone()); @@ -317,6 +317,7 @@ fn main() { let node_runtime = NodeRuntime::new(client.http_client(), rx); language::init(cx); + language_extension::init(extension_host_proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); @@ -326,7 +327,6 @@ fn main() { zed::init(cx); project::Project::init(&client, cx); client::init(&client, cx); - language::init(cx); let telemetry = client.telemetry(); telemetry.start( system_id.as_ref().map(|id| id.to_string()), @@ -376,6 +376,11 @@ fn main() { SystemAppearance::init(cx); theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); + theme_extension::init( + extension_host_proxy.clone(), + ThemeRegistry::global(cx), + cx.background_executor().clone(), + ); command_palette::init(cx); let copilot_language_server_id = app_state.languages.next_language_server_id(); copilot::init( @@ -407,17 +412,8 @@ fn main() { app_state.client.telemetry().clone(), cx, ); - let api = extensions_ui::ConcreteExtensionRegistrationHooks::new( - ThemeRegistry::global(cx), - SlashCommandRegistry::global(cx), - IndexedDocsRegistry::global(cx), - SnippetRegistry::global(cx), - app_state.languages.clone(), - ContextServerFactoryRegistry::global(cx), - cx, - ); extension_host::init( - api, + extension_host_proxy, app_state.fs.clone(), app_state.client.clone(), app_state.node_runtime.clone(),