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
This commit is contained in:
Marshall Bowers 2024-11-22 19:02:32 -05:00 committed by GitHub
parent c9f2c2792c
commit 1cfcdfa7ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 874 additions and 591 deletions

49
Cargo.lock generated
View File

@ -2601,6 +2601,7 @@ dependencies = [
"editor", "editor",
"env_logger 0.11.5", "env_logger 0.11.5",
"envy", "envy",
"extension",
"file_finder", "file_finder",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
@ -2842,6 +2843,7 @@ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"command_palette_hooks", "command_palette_hooks",
"extension",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"log", "log",
@ -4127,6 +4129,7 @@ dependencies = [
"language", "language",
"log", "log",
"lsp", "lsp",
"parking_lot",
"semantic_version", "semantic_version",
"serde", "serde",
"serde_json", "serde_json",
@ -4178,6 +4181,7 @@ dependencies = [
"gpui", "gpui",
"http_client", "http_client",
"language", "language",
"language_extension",
"log", "log",
"lsp", "lsp",
"node_runtime", "node_runtime",
@ -4196,6 +4200,7 @@ dependencies = [
"task", "task",
"tempfile", "tempfile",
"theme", "theme",
"theme_extension",
"toml 0.8.19", "toml 0.8.19",
"url", "url",
"util", "util",
@ -4209,21 +4214,15 @@ name = "extensions_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assistant_slash_command",
"client", "client",
"collections", "collections",
"context_servers",
"db", "db",
"editor", "editor",
"extension",
"extension_host", "extension_host",
"fs", "fs",
"fuzzy", "fuzzy",
"gpui", "gpui",
"indexed_docs",
"language", "language",
"log",
"lsp",
"num-format", "num-format",
"picker", "picker",
"project", "project",
@ -4232,7 +4231,6 @@ dependencies = [
"serde", "serde",
"settings", "settings",
"smallvec", "smallvec",
"snippet_provider",
"theme", "theme",
"ui", "ui",
"util", "util",
@ -6533,6 +6531,23 @@ dependencies = [
"util", "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]] [[package]]
name = "language_model" name = "language_model"
version = "0.1.0" version = "0.1.0"
@ -9853,6 +9868,7 @@ dependencies = [
"client", "client",
"clock", "clock",
"env_logger 0.11.5", "env_logger 0.11.5",
"extension",
"extension_host", "extension_host",
"fork", "fork",
"fs", "fs",
@ -9862,6 +9878,7 @@ dependencies = [
"gpui", "gpui",
"http_client", "http_client",
"language", "language",
"language_extension",
"languages", "languages",
"libc", "libc",
"log", "log",
@ -11304,6 +11321,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"extension",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@ -12357,6 +12375,17 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "theme_extension"
version = "0.1.0"
dependencies = [
"anyhow",
"extension",
"fs",
"gpui",
"theme",
]
[[package]] [[package]]
name = "theme_importer" name = "theme_importer"
version = "0.1.0" version = "0.1.0"
@ -15466,7 +15495,6 @@ dependencies = [
"ashpd", "ashpd",
"assets", "assets",
"assistant", "assistant",
"assistant_slash_command",
"async-watch", "async-watch",
"audio", "audio",
"auto_update", "auto_update",
@ -15483,12 +15511,12 @@ dependencies = [
"collections", "collections",
"command_palette", "command_palette",
"command_palette_hooks", "command_palette_hooks",
"context_servers",
"copilot", "copilot",
"db", "db",
"diagnostics", "diagnostics",
"editor", "editor",
"env_logger 0.11.5", "env_logger 0.11.5",
"extension",
"extension_host", "extension_host",
"extensions_ui", "extensions_ui",
"feature_flags", "feature_flags",
@ -15503,11 +15531,11 @@ dependencies = [
"gpui", "gpui",
"http_client", "http_client",
"image_viewer", "image_viewer",
"indexed_docs",
"inline_completion_button", "inline_completion_button",
"install_cli", "install_cli",
"journal", "journal",
"language", "language",
"language_extension",
"language_model", "language_model",
"language_models", "language_models",
"language_selector", "language_selector",
@ -15556,6 +15584,7 @@ dependencies = [
"telemetry_events", "telemetry_events",
"terminal_view", "terminal_view",
"theme", "theme",
"theme_extension",
"theme_selector", "theme_selector",
"time", "time",
"toolchain_selector", "toolchain_selector",

View File

@ -55,6 +55,7 @@ members = [
"crates/install_cli", "crates/install_cli",
"crates/journal", "crates/journal",
"crates/language", "crates/language",
"crates/language_extension",
"crates/language_model", "crates/language_model",
"crates/language_models", "crates/language_models",
"crates/language_selector", "crates/language_selector",
@ -116,6 +117,7 @@ members = [
"crates/terminal_view", "crates/terminal_view",
"crates/text", "crates/text",
"crates/theme", "crates/theme",
"crates/theme_extension",
"crates/theme_importer", "crates/theme_importer",
"crates/theme_selector", "crates/theme_selector",
"crates/time_format", "crates/time_format",
@ -230,6 +232,7 @@ inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" } install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" } journal = { path = "crates/journal" }
language = { path = "crates/language" } language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" } language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" } language_models = { path = "crates/language_models" }
language_selector = { path = "crates/language_selector" } language_selector = { path = "crates/language_selector" }
@ -292,6 +295,7 @@ terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" } terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" } text = { path = "crates/text" }
theme = { path = "crates/theme" } theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
theme_importer = { path = "crates/theme_importer" } theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" } theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" } time_format = { path = "crates/time_format" }

View File

@ -33,7 +33,6 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs; use fs::Fs;
use gpui::impl_actions; use gpui::impl_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*; pub(crate) use inline_assistant::*;
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
@ -275,7 +274,7 @@ pub fn init(
client.telemetry().clone(), client.telemetry().clone(),
cx, cx,
); );
IndexedDocsRegistry::init_global(cx); indexed_docs::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| { CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Assistant::NAMESPACE); filter.hide_namespace(Assistant::NAMESPACE);

View File

@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx); SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx);
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]

View File

@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use extension::{Extension, WorktreeDelegate}; use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{Task, WeakView, WindowContext}; use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate}; use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*; use ui::prelude::*;
use workspace::Workspace; use workspace::Workspace;
use crate::{ use crate::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, 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<SlashCommandRegistry>,
}
impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
fn register_slash_command(
&self,
extension: Arc<dyn Extension>,
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`]. /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>); struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);

View File

@ -90,6 +90,7 @@ collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true ctor.workspace = true
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true env_logger.workspace = true
extension.workspace = true
file_finder.workspace = true file_finder.workspace = true
fs = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] } git = { workspace = true, features = ["test-support"] }

View File

@ -1,6 +1,7 @@
use crate::tests::TestServer; use crate::tests::TestServer;
use call::ActiveCall; use call::ActiveCall;
use collections::HashSet; use collections::HashSet;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _}; use fs::{FakeFs, Fs as _};
use futures::StreamExt as _; use futures::StreamExt as _;
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal 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, http_client: remote_http_client,
node_runtime: node, node_runtime: node,
languages, languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
}, },
cx, cx,
) )
@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches(
http_client: remote_http_client, http_client: remote_http_client,
node_runtime: node, node_runtime: node,
languages, languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
}, },
cx, cx,
) )
@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
http_client: remote_http_client, http_client: remote_http_client,
node_runtime: NodeRuntime::unavailable(), node_runtime: NodeRuntime::unavailable(),
languages, languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
}, },
cx, cx,
) )

View File

@ -15,6 +15,7 @@ path = "src/context_servers.rs"
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
extension.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
log.workspace = true log.workspace = true

View File

@ -1,4 +1,5 @@
pub mod client; pub mod client;
mod extension_context_server;
pub mod manager; pub mod manager;
pub mod protocol; pub mod protocol;
mod registry; mod registry;
@ -19,6 +20,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
ContextServerSettings::register(cx); ContextServerSettings::register(cx);
ContextServerFactoryRegistry::default_global(cx); ContextServerFactoryRegistry::default_global(cx);
extension_context_server::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| { CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE); filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);

View File

@ -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<u64>,
}
impl ProjectDelegate for ExtensionProject {
fn worktree_ids(&self) -> Vec<u64> {
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<ContextServerFactoryRegistry>,
}
impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
id: Arc<str>,
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()),
})
})
}
}),
)
});
}
}

View File

@ -24,6 +24,7 @@ http_client.workspace = true
language.workspace = true language.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true
parking_lot.workspace = true
semantic_version.workspace = true semantic_version.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View File

@ -1,4 +1,5 @@
pub mod extension_builder; pub mod extension_builder;
mod extension_host_proxy;
mod extension_manifest; mod extension_manifest;
mod types; mod types;
@ -9,13 +10,19 @@ use ::lsp::LanguageServerName;
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use fs::normalize_path; use fs::normalize_path;
use gpui::Task; use gpui::{AppContext, Task};
use language::LanguageName; use language::LanguageName;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
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.
pub fn init(cx: &mut AppContext) {
ExtensionHostProxy::default_global(cx);
}
#[async_trait] #[async_trait]
pub trait WorktreeDelegate: Send + Sync + 'static { pub trait WorktreeDelegate: Send + Sync + 'static {
fn id(&self) -> u64; fn id(&self) -> u64;

View File

@ -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<ExtensionHostProxy>);
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<Option<Arc<dyn ExtensionThemeProxy>>>,
grammar_proxy: RwLock<Option<Arc<dyn ExtensionGrammarProxy>>>,
language_proxy: RwLock<Option<Arc<dyn ExtensionLanguageProxy>>>,
language_server_proxy: RwLock<Option<Arc<dyn ExtensionLanguageServerProxy>>>,
snippet_proxy: RwLock<Option<Arc<dyn ExtensionSnippetProxy>>>,
slash_command_proxy: RwLock<Option<Arc<dyn ExtensionSlashCommandProxy>>>,
context_server_proxy: RwLock<Option<Arc<dyn ExtensionContextServerProxy>>>,
indexed_docs_provider_proxy: RwLock<Option<Arc<dyn ExtensionIndexedDocsProviderProxy>>>,
}
impl ExtensionHostProxy {
/// Returns the global [`ExtensionHostProxy`].
pub fn global(cx: &AppContext) -> Arc<Self> {
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<Self> {
cx.default_global::<GlobalExtensionHostProxy>().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<dyn Fs>) -> Task<Result<Vec<String>>>;
fn remove_user_themes(&self, themes: Vec<SharedString>);
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>>;
fn reload_current_theme(&self, cx: &mut AppContext);
}
impl ExtensionThemeProxy for ExtensionHostProxy {
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
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<SharedString>) {
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<dyn Fs>) -> Task<Result<()>> {
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<str>, PathBuf)>);
}
impl ExtensionGrammarProxy for ExtensionHostProxy {
fn register_grammars(&self, grammars: Vec<(Arc<str>, 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<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
);
fn remove_languages(
&self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
);
}
impl ExtensionLanguageProxy for ExtensionHostProxy {
fn register_language(
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 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<str>],
) {
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<dyn Extension>,
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<dyn Extension>,
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<dyn Extension>, command: SlashCommand);
}
impl ExtensionSlashCommandProxy for ExtensionHostProxy {
fn register_slash_command(&self, extension: Arc<dyn Extension>, 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<dyn Extension>,
server_id: Arc<str>,
cx: &mut AppContext,
);
}
impl ExtensionContextServerProxy for ExtensionHostProxy {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
server_id: Arc<str>,
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<dyn Extension>, provider_id: Arc<str>);
}
impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
return;
};
proxy.register_indexed_docs_provider(extension, provider_id)
}
}

View File

@ -57,7 +57,9 @@ env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] }
language_extension.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true reqwest_client.workspace = true
theme = { workspace = true, features = ["test-support"] } theme = { workspace = true, features = ["test-support"] }
theme_extension.workspace = true

View File

@ -1,4 +1,3 @@
pub mod extension_lsp_adapter;
pub mod extension_settings; pub mod extension_settings;
pub mod headless_host; pub mod headless_host;
pub mod wasm_host; pub mod wasm_host;
@ -12,8 +11,12 @@ use async_tar::Archive;
use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashMap, HashSet}; use collections::{btree_map, BTreeMap, HashMap, HashSet};
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use extension::Extension;
pub use extension::ExtensionManifest; pub use extension::ExtensionManifest;
use extension::{
ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy,
ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,
};
use fs::{Fs, RemoveOptions}; use fs::{Fs, RemoveOptions};
use futures::{ use futures::{
channel::{ channel::{
@ -24,15 +27,14 @@ use futures::{
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _, select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
}; };
use gpui::{ use gpui::{
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
SharedString, Task, WeakModel, WeakModel,
}; };
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{ use language::{
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
QUERY_FILENAME_PREFIXES, QUERY_FILENAME_PREFIXES,
}; };
use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks; use project::ContextProviderWithTasks;
use release_channel::ReleaseChannel; use release_channel::ReleaseChannel;
@ -95,82 +97,8 @@ pub fn is_version_compatible(
true true
} }
pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn list_theme_names(
&self,
_theme_path: PathBuf,
_fs: Arc<dyn Fs>,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
}
fn reload_current_theme(&self, _cx: &mut AppContext) {}
fn register_language(
&self,
_language: LanguageName,
_grammar: Option<Arc<str>>,
_matcher: language::LanguageMatcher,
_load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
}
fn register_lsp_adapter(
&self,
_extension: Arc<dyn Extension>,
_language_server_id: LanguageServerName,
_language: LanguageName,
) {
}
fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {}
fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
fn remove_languages(
&self,
_languages_to_remove: &[LanguageName],
_grammars_to_remove: &[Arc<str>],
) {
}
fn register_slash_command(
&self,
_extension: Arc<dyn Extension>,
_command: extension::SlashCommand,
) {
}
fn register_context_server(
&self,
_extension: Arc<dyn Extension>,
_id: Arc<str>,
_cx: &mut AppContext,
) {
}
fn register_docs_provider(&self, _extension: Arc<dyn Extension>, _provider_id: Arc<str>) {}
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 struct ExtensionStore {
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>, pub proxy: Arc<ExtensionHostProxy>,
pub builder: Arc<ExtensionBuilder>, pub builder: Arc<ExtensionBuilder>,
pub extension_index: ExtensionIndex, pub extension_index: ExtensionIndex,
pub fs: Arc<dyn Fs>, pub fs: Arc<dyn Fs>,
@ -240,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry {
actions!(zed, [ReloadExtensions]); actions!(zed, [ReloadExtensions]);
pub fn init( pub fn init(
registration_hooks: Arc<dyn ExtensionRegistrationHooks>, extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
client: Arc<Client>, client: Arc<Client>,
node_runtime: NodeRuntime, node_runtime: NodeRuntime,
@ -252,7 +180,7 @@ pub fn init(
ExtensionStore::new( ExtensionStore::new(
paths::extensions_dir().clone(), paths::extensions_dir().clone(),
None, None,
registration_hooks, extension_host_proxy,
fs, fs,
client.http_client().clone(), client.http_client().clone(),
client.http_client().clone(), client.http_client().clone(),
@ -284,7 +212,7 @@ impl ExtensionStore {
pub fn new( pub fn new(
extensions_dir: PathBuf, extensions_dir: PathBuf,
build_dir: Option<PathBuf>, build_dir: Option<PathBuf>,
extension_api: Arc<dyn ExtensionRegistrationHooks>, extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
http_client: Arc<HttpClientWithUrl>, http_client: Arc<HttpClientWithUrl>,
builder_client: Arc<dyn HttpClient>, builder_client: Arc<dyn HttpClient>,
@ -300,7 +228,7 @@ impl ExtensionStore {
let (reload_tx, mut reload_rx) = unbounded(); let (reload_tx, mut reload_rx) = unbounded();
let (connection_registered_tx, mut connection_registered_rx) = unbounded(); let (connection_registered_tx, mut connection_registered_rx) = unbounded();
let mut this = Self { let mut this = Self {
registration_hooks: extension_api.clone(), proxy: extension_host_proxy.clone(),
extension_index: Default::default(), extension_index: Default::default(),
installed_dir, installed_dir,
index_path, index_path,
@ -312,7 +240,7 @@ impl ExtensionStore {
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
node_runtime, node_runtime,
extension_api, extension_host_proxy,
work_dir, work_dir,
cx, cx,
), ),
@ -1113,16 +1041,16 @@ impl ExtensionStore {
grammars_to_remove.extend(extension.manifest.grammars.keys().cloned()); grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
for (language_server_name, config) in extension.manifest.language_servers.iter() { for (language_server_name, config) in extension.manifest.language_servers.iter() {
for language in config.languages() { for language in config.languages() {
self.registration_hooks self.proxy
.remove_lsp_adapter(&language, language_server_name); .remove_language_server(&language, language_server_name);
} }
} }
} }
self.wasm_extensions self.wasm_extensions
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id)); .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
self.registration_hooks.remove_user_themes(themes_to_remove); self.proxy.remove_user_themes(themes_to_remove);
self.registration_hooks self.proxy
.remove_languages(&languages_to_remove, &grammars_to_remove); .remove_languages(&languages_to_remove, &grammars_to_remove);
let languages_to_add = new_index let languages_to_add = new_index
@ -1157,8 +1085,7 @@ impl ExtensionStore {
})); }));
} }
self.registration_hooks self.proxy.register_grammars(grammars_to_add);
.register_wasm_grammars(grammars_to_add);
for (language_name, language) in languages_to_add { for (language_name, language) in languages_to_add {
let mut language_path = self.installed_dir.clone(); let mut language_path = self.installed_dir.clone();
@ -1166,7 +1093,7 @@ impl ExtensionStore {
Path::new(language.extension.as_ref()), Path::new(language.extension.as_ref()),
language.path.as_path(), language.path.as_path(),
]); ]);
self.registration_hooks.register_language( self.proxy.register_language(
language_name.clone(), language_name.clone(),
language.grammar.clone(), language.grammar.clone(),
language.matcher.clone(), language.matcher.clone(),
@ -1196,7 +1123,7 @@ impl ExtensionStore {
let fs = self.fs.clone(); let fs = self.fs.clone();
let wasm_host = self.wasm_host.clone(); let wasm_host = self.wasm_host.clone();
let root_dir = self.installed_dir.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 let extension_entries = extensions_to_load
.iter() .iter()
.filter_map(|name| new_index.extensions.get(name).cloned()) .filter_map(|name| new_index.extensions.get(name).cloned())
@ -1212,13 +1139,17 @@ impl ExtensionStore {
let fs = fs.clone(); let fs = fs.clone();
async move { async move {
for theme_path in themes_to_add.into_iter() { 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 { for snippets_path in &snippets_to_add {
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err() 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(); .log_err();
} }
} }
@ -1259,7 +1190,7 @@ impl ExtensionStore {
for (language_server_id, language_server_config) in &manifest.language_servers { for (language_server_id, language_server_config) in &manifest.language_servers {
for language in language_server_config.languages() { for language in language_server_config.languages() {
this.registration_hooks.register_lsp_adapter( this.proxy.register_language_server(
extension.clone(), extension.clone(),
language_server_id.clone(), language_server_id.clone(),
language.clone(), language.clone(),
@ -1268,7 +1199,7 @@ impl ExtensionStore {
} }
for (slash_command_name, slash_command) in &manifest.slash_commands { for (slash_command_name, slash_command) in &manifest.slash_commands {
this.registration_hooks.register_slash_command( this.proxy.register_slash_command(
extension.clone(), extension.clone(),
extension::SlashCommand { extension::SlashCommand {
name: slash_command_name.to_string(), name: slash_command_name.to_string(),
@ -1283,21 +1214,18 @@ impl ExtensionStore {
} }
for (id, _context_server_entry) in &manifest.context_servers { for (id, _context_server_entry) in &manifest.context_servers {
this.registration_hooks.register_context_server( this.proxy
extension.clone(), .register_context_server(extension.clone(), id.clone(), cx);
id.clone(),
cx,
);
} }
for (provider_id, _provider) in &manifest.indexed_docs_providers { for (provider_id, _provider) in &manifest.indexed_docs_providers {
this.registration_hooks this.proxy
.register_docs_provider(extension.clone(), provider_id.clone()); .register_indexed_docs_provider(extension.clone(), provider_id.clone());
} }
} }
this.wasm_extensions.extend(wasm_extensions); this.wasm_extensions.extend(wasm_extensions);
this.registration_hooks.reload_current_theme(cx); this.proxy.reload_current_theme(cx);
}) })
.ok(); .ok();
}) })
@ -1308,7 +1236,7 @@ impl ExtensionStore {
let work_dir = self.wasm_host.work_dir.clone(); let work_dir = self.wasm_host.work_dir.clone();
let extensions_dir = self.installed_dir.clone(); let extensions_dir = self.installed_dir.clone();
let index_path = self.index_path.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 { cx.background_executor().spawn(async move {
let start_time = Instant::now(); let start_time = Instant::now();
let mut index = ExtensionIndex::default(); let mut index = ExtensionIndex::default();
@ -1334,7 +1262,7 @@ impl ExtensionStore {
fs.clone(), fs.clone(),
extension_dir, extension_dir,
&mut index, &mut index,
extension_api.clone(), proxy.clone(),
) )
.await .await
.log_err(); .log_err();
@ -1357,7 +1285,7 @@ impl ExtensionStore {
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
extension_dir: PathBuf, extension_dir: PathBuf,
index: &mut ExtensionIndex, index: &mut ExtensionIndex,
extension_api: Arc<dyn ExtensionRegistrationHooks>, proxy: Arc<ExtensionHostProxy>,
) -> Result<()> { ) -> Result<()> {
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?; let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
let extension_id = extension_manifest.id.clone(); let extension_id = extension_manifest.id.clone();
@ -1409,7 +1337,7 @@ impl ExtensionStore {
continue; continue;
}; };
let Some(theme_families) = extension_api let Some(theme_families) = proxy
.list_theme_names(theme_path.clone(), fs.clone()) .list_theme_names(theme_path.clone(), fs.clone())
.await .await
.log_err() .log_err()

View File

@ -1,20 +1,16 @@
use crate::extension_lsp_adapter::ExtensionLspAdapter;
use crate::{ use crate::{
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry, Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore, ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore,
GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION, GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION,
}; };
use anyhow::Result;
use async_compression::futures::bufread::GzipEncoder; use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap; use collections::BTreeMap;
use extension::Extension; use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs, RealFs}; use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt}; 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 http_client::{FakeHttpClient, Response};
use language::{ use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus};
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage,
};
use lsp::LanguageServerName; use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -31,91 +27,6 @@ use std::{
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::test::temp_tree; use util::test::temp_tree;
use crate::ExtensionRegistrationHooks;
struct TestExtensionRegistrationHooks {
executor: BackgroundExecutor,
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
}
impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks {
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
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<dyn fs::Fs>) -> Task<Result<()>> {
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<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn register_language(
&self,
language: language::LanguageName,
grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + '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<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
fn register_lsp_adapter(
&self,
extension: Arc<dyn Extension>,
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)] #[cfg(test)]
#[ctor::ctor] #[ctor::ctor]
fn init_logger() { fn init_logger() {
@ -347,20 +258,18 @@ async fn test_extension_store(cx: &mut TestAppContext) {
.collect(), .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 theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let registration_hooks = Arc::new(TestExtensionRegistrationHooks { theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
executor: cx.executor(), let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
language_registry: language_registry.clone(), language_extension::init(proxy.clone(), language_registry.clone());
theme_registry: theme_registry.clone(),
});
let node_runtime = NodeRuntime::unavailable(); let node_runtime = NodeRuntime::unavailable();
let store = cx.new_model(|cx| { let store = cx.new_model(|cx| {
ExtensionStore::new( ExtensionStore::new(
PathBuf::from("/the-extension-dir"), PathBuf::from("/the-extension-dir"),
None, None,
registration_hooks.clone(), proxy.clone(),
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
http_client.clone(), http_client.clone(),
@ -485,7 +394,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
ExtensionStore::new( ExtensionStore::new(
PathBuf::from("/the-extension-dir"), PathBuf::from("/the-extension-dir"),
None, None,
registration_hooks, proxy,
fs.clone(), fs.clone(),
http_client.clone(), http_client.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 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 theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let registration_hooks = Arc::new(TestExtensionRegistrationHooks { theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
executor: cx.executor(), let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
language_registry: language_registry.clone(), language_extension::init(proxy.clone(), language_registry.clone());
theme_registry: theme_registry.clone(),
});
let node_runtime = NodeRuntime::unavailable(); let node_runtime = NodeRuntime::unavailable();
let mut status_updates = language_registry.language_server_binary_statuses(); 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( ExtensionStore::new(
extensions_dir.clone(), extensions_dir.clone(),
Some(cache_dir), Some(cache_dir),
registration_hooks, proxy,
fs.clone(), fs.clone(),
extension_client.clone(), extension_client.clone(),
builder_client, builder_client,

View File

@ -3,29 +3,18 @@ use std::{path::PathBuf, sync::Arc};
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use client::{proto, TypedEnvelope}; use client::{proto, TypedEnvelope};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use extension::{Extension, ExtensionManifest}; use extension::{
Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
ExtensionManifest,
};
use fs::{Fs, RemoveOptions, RenameOptions}; use fs::{Fs, RemoveOptions, RenameOptions};
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel}; use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel};
use http_client::HttpClient; use http_client::HttpClient;
use language::{LanguageConfig, LanguageName, LanguageQueries, LanguageRegistry, LoadedLanguage}; use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage};
use lsp::LanguageServerName; use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use crate::{ use crate::wasm_host::{WasmExtension, WasmHost};
extension_lsp_adapter::ExtensionLspAdapter,
wasm_host::{WasmExtension, WasmHost},
ExtensionRegistrationHooks,
};
pub struct HeadlessExtensionStore {
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
pub fs: Arc<dyn Fs>,
pub extension_dir: PathBuf,
pub wasm_host: Arc<WasmHost>,
pub loaded_extensions: HashMap<Arc<str>, Arc<str>>,
pub loaded_languages: HashMap<Arc<str>, Vec<LanguageName>>,
pub loaded_language_servers: HashMap<Arc<str>, Vec<(LanguageServerName, LanguageName)>>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ExtensionVersion { pub struct ExtensionVersion {
@ -34,28 +23,37 @@ pub struct ExtensionVersion {
pub dev: bool, pub dev: bool,
} }
pub struct HeadlessExtensionStore {
pub fs: Arc<dyn Fs>,
pub extension_dir: PathBuf,
pub proxy: Arc<ExtensionHostProxy>,
pub wasm_host: Arc<WasmHost>,
pub loaded_extensions: HashMap<Arc<str>, Arc<str>>,
pub loaded_languages: HashMap<Arc<str>, Vec<LanguageName>>,
pub loaded_language_servers: HashMap<Arc<str>, Vec<(LanguageServerName, LanguageName)>>,
}
impl HeadlessExtensionStore { impl HeadlessExtensionStore {
pub fn new( pub fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
languages: Arc<LanguageRegistry>,
extension_dir: PathBuf, extension_dir: PathBuf,
extension_host_proxy: Arc<ExtensionHostProxy>,
node_runtime: NodeRuntime, node_runtime: NodeRuntime,
cx: &mut AppContext, cx: &mut AppContext,
) -> Model<Self> { ) -> Model<Self> {
let registration_hooks = Arc::new(HeadlessRegistrationHooks::new(languages.clone()));
cx.new_model(|cx| Self { cx.new_model(|cx| Self {
registration_hooks: registration_hooks.clone(),
fs: fs.clone(), fs: fs.clone(),
wasm_host: WasmHost::new( wasm_host: WasmHost::new(
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
node_runtime, node_runtime,
registration_hooks, extension_host_proxy.clone(),
extension_dir.join("work"), extension_dir.join("work"),
cx, cx,
), ),
extension_dir, extension_dir,
proxy: extension_host_proxy,
loaded_extensions: Default::default(), loaded_extensions: Default::default(),
loaded_languages: Default::default(), loaded_languages: Default::default(),
loaded_language_servers: Default::default(), loaded_language_servers: Default::default(),
@ -154,7 +152,7 @@ impl HeadlessExtensionStore {
config.grammar = None; config.grammar = None;
this.registration_hooks.register_language( this.proxy.register_language(
config.name.clone(), config.name.clone(),
None, None,
config.matcher.clone(), config.matcher.clone(),
@ -184,7 +182,7 @@ impl HeadlessExtensionStore {
.entry(manifest.id.clone()) .entry(manifest.id.clone())
.or_default() .or_default()
.push((language_server_id.clone(), language.clone())); .push((language_server_id.clone(), language.clone()));
this.registration_hooks.register_lsp_adapter( this.proxy.register_language_server(
wasm_extension.clone(), wasm_extension.clone(),
language_server_id.clone(), language_server_id.clone(),
language.clone(), language.clone(),
@ -202,19 +200,20 @@ impl HeadlessExtensionStore {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
self.loaded_extensions.remove(extension_id); self.loaded_extensions.remove(extension_id);
let languages_to_remove = self let languages_to_remove = self
.loaded_languages .loaded_languages
.remove(extension_id) .remove(extension_id)
.unwrap_or_default(); .unwrap_or_default();
self.registration_hooks self.proxy.remove_languages(&languages_to_remove, &[]);
.remove_languages(&languages_to_remove, &[]);
for (language_server_name, language) in self for (language_server_name, language) in self
.loaded_language_servers .loaded_language_servers
.remove(extension_id) .remove(extension_id)
.unwrap_or_default() .unwrap_or_default()
{ {
self.registration_hooks self.proxy
.remove_lsp_adapter(&language, &language_server_name); .remove_language_server(&language, &language_server_name);
} }
let path = self.extension_dir.join(&extension_id.to_string()); let path = self.extension_dir.join(&extension_id.to_string());
@ -318,71 +317,3 @@ impl HeadlessExtensionStore {
Ok(proto::Ack {}) Ok(proto::Ack {})
} }
} }
struct HeadlessRegistrationHooks {
language_registry: Arc<LanguageRegistry>,
}
impl HeadlessRegistrationHooks {
fn new(language_registry: Arc<LanguageRegistry>) -> Self {
Self { language_registry }
}
}
impl ExtensionRegistrationHooks for HeadlessRegistrationHooks {
fn register_language(
&self,
language: LanguageName,
_grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
log::info!("registering language: {:?}", language);
self.language_registry
.register_language(language, None, matcher, load)
}
fn register_lsp_adapter(
&self,
extension: Arc<dyn Extension>,
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<str>, 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<str>],
) {
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)
}
}

View File

@ -1,11 +1,11 @@
pub mod wit; pub mod wit;
use crate::{ExtensionManifest, ExtensionRegistrationHooks}; use crate::ExtensionManifest;
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use extension::{ use extension::{
CodeLabel, Command, Completion, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate,
SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
}; };
use fs::{normalize_path, Fs}; use fs::{normalize_path, Fs};
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
@ -40,7 +40,7 @@ pub struct WasmHost {
release_channel: ReleaseChannel, release_channel: ReleaseChannel,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime, node_runtime: NodeRuntime,
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>, pub(crate) proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
pub work_dir: PathBuf, pub work_dir: PathBuf,
_main_thread_message_task: Task<()>, _main_thread_message_task: Task<()>,
@ -330,7 +330,7 @@ impl WasmHost {
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime, node_runtime: NodeRuntime,
registration_hooks: Arc<dyn ExtensionRegistrationHooks>, proxy: Arc<ExtensionHostProxy>,
work_dir: PathBuf, work_dir: PathBuf,
cx: &mut AppContext, cx: &mut AppContext,
) -> Arc<Self> { ) -> Arc<Self> {
@ -346,7 +346,7 @@ impl WasmHost {
work_dir, work_dir,
http_client, http_client,
node_runtime, node_runtime,
registration_hooks, proxy,
release_channel: ReleaseChannel::global(cx), release_channel: ReleaseChannel::global(cx),
_main_thread_message_task: task, _main_thread_message_task: task,
main_thread_message_tx: tx, main_thread_message_tx: tx,

View File

@ -3,7 +3,7 @@ use crate::wasm_host::wit::since_v0_0_4;
use crate::wasm_host::WasmState; use crate::wasm_host::WasmState;
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use extension::WorktreeDelegate; use extension::{ExtensionLanguageServerProxy, WorktreeDelegate};
use language::LanguageServerBinaryStatus; use language::LanguageServerBinaryStatus;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
@ -149,8 +149,9 @@ impl ExtensionImports for WasmState {
}; };
self.host self.host
.registration_hooks .proxy
.update_lsp_status(lsp::LanguageServerName(server_name.into()), status); .update_language_server_status(lsp::LanguageServerName(server_name.into()), status);
Ok(()) Ok(())
} }

View File

@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use async_trait::async_trait; use async_trait::async_trait;
use extension::{KeyValueStoreDelegate, WorktreeDelegate}; use extension::{ExtensionLanguageServerProxy, KeyValueStoreDelegate, WorktreeDelegate};
use futures::{io::BufReader, FutureExt as _}; use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt}; use futures::{lock::Mutex, AsyncReadExt};
use language::LanguageName; use language::LanguageName;
@ -495,8 +495,9 @@ impl ExtensionImports for WasmState {
}; };
self.host self.host
.registration_hooks .proxy
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
Ok(()) Ok(())
} }

View File

@ -8,7 +8,9 @@ use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use async_trait::async_trait; use async_trait::async_trait;
use context_servers::manager::ContextServerSettings; use context_servers::manager::ContextServerSettings;
use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate}; use extension::{
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
};
use futures::{io::BufReader, FutureExt as _}; use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt}; use futures::{lock::Mutex, AsyncReadExt};
use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus}; use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus};
@ -682,8 +684,9 @@ impl ExtensionImports for WasmState {
}; };
self.host self.host
.registration_hooks .proxy
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
Ok(()) Ok(())
} }

View File

@ -13,21 +13,15 @@ path = "src/extensions_ui.rs"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
assistant_slash_command.workspace = true
client.workspace = true client.workspace = true
collections.workspace = true collections.workspace = true
context_servers.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
fs.workspace = true fs.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
gpui.workspace = true gpui.workspace = true
indexed_docs.workspace = true
language.workspace = true language.workspace = true
log.workspace = true
lsp.workspace = true
num-format.workspace = true num-format.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
@ -36,7 +30,6 @@ semantic_version.workspace = true
serde.workspace = true serde.workspace = true
settings.workspace = true settings.workspace = true
smallvec.workspace = true smallvec.workspace = true
snippet_provider.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true

View File

@ -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<u64>,
}
impl ProjectDelegate for ExtensionProject {
fn worktree_ids(&self) -> Vec<u64> {
self.worktree_ids.clone()
}
}
pub struct ConcreteExtensionRegistrationHooks {
slash_command_registry: Arc<SlashCommandRegistry>,
theme_registry: Arc<ThemeRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
language_registry: Arc<LanguageRegistry>,
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
executor: BackgroundExecutor,
}
impl ConcreteExtensionRegistrationHooks {
pub fn new(
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
language_registry: Arc<LanguageRegistry>,
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
cx: &AppContext,
) -> Arc<dyn extension_host::ExtensionRegistrationHooks> {
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<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
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<dyn Extension>,
command: extension::SlashCommand,
) {
self.slash_command_registry
.register_command(ExtensionSlashCommand::new(extension, command), false)
}
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
id: Arc<str>,
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<dyn Extension>, provider_id: Arc<str>) {
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<dyn Extension>,
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<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
fn register_language(
&self,
language: language::LanguageName,
grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + '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<dyn Fs>) -> Task<Result<Vec<String>>> {
self.executor.spawn(async move {
let themes = theme::read_user_theme(&path, fs).await?;
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
})
}
}

View File

@ -1,10 +1,7 @@
mod components; mod components;
mod extension_registration_hooks;
mod extension_suggest; mod extension_suggest;
mod extension_version_selector; mod extension_version_selector;
pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::OnceLock; use std::sync::OnceLock;
use std::time::Duration; use std::time::Duration;

View File

@ -3,9 +3,33 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use async_trait::async_trait; 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<IndexedDocsRegistry>,
}
impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy {
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
self.indexed_docs_registry
.register_provider(Box::new(ExtensionIndexedDocsProvider::new(
extension,
ProviderId(provider_id),
)));
}
}
pub struct ExtensionIndexedDocsProvider { pub struct ExtensionIndexedDocsProvider {
extension: Arc<dyn Extension>, extension: Arc<dyn Extension>,

View File

@ -3,7 +3,14 @@ mod providers;
mod registry; mod registry;
mod store; mod store;
use gpui::AppContext;
pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider; pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
pub use crate::providers::rustdoc::*; pub use crate::providers::rustdoc::*;
pub use crate::registry::*; pub use crate::registry::*;
pub use crate::store::*; pub use crate::store::*;
pub fn init(cx: &mut AppContext) {
IndexedDocsRegistry::init_global(cx);
extension_indexed_docs_provider::init(cx);
}

View File

@ -20,7 +20,7 @@ impl IndexedDocsRegistry {
GlobalIndexedDocsRegistry::global(cx).0.clone() GlobalIndexedDocsRegistry::global(cx).0.clone()
} }
pub fn init_global(cx: &mut AppContext) { pub(crate) fn init_global(cx: &mut AppContext) {
GlobalIndexedDocsRegistry::set_global( GlobalIndexedDocsRegistry::set_global(
cx, cx,
GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))), GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))),

View File

@ -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

View File

@ -0,0 +1 @@
../../LICENSE-GPL

View File

@ -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 anyhow::{Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use collections::HashMap; use collections::HashMap;
use extension::{Extension, WorktreeDelegate}; use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
use futures::{Future, FutureExt}; use futures::{Future, FutureExt};
use gpui::AsyncAppContext; use gpui::AsyncAppContext;
use language::{ use language::{
CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter, CodeLabel, HighlightId, Language, LanguageName, LanguageServerBinaryStatus,
LspAdapterDelegate, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
}; };
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName}; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use std::ops::Range;
use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc};
use util::{maybe, ResultExt}; use util::{maybe, ResultExt};
use crate::LanguageServerRegistryProxy;
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
pub struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>); struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
#[async_trait] #[async_trait]
impl WorktreeDelegate for WorktreeDelegateAdapter { 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<dyn Extension>,
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<dyn Extension>, extension: Arc<dyn Extension>,
language_server_id: LanguageServerName, language_server_id: LanguageServerName,
language_name: LanguageName, language_name: LanguageName,
} }
impl ExtensionLspAdapter { impl ExtensionLspAdapter {
pub fn new( fn new(
extension: Arc<dyn Extension>, extension: Arc<dyn Extension>,
language_server_id: LanguageServerName, language_server_id: LanguageServerName,
language_name: LanguageName, language_name: LanguageName,

View File

@ -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<ExtensionHostProxy>,
language_registry: Arc<LanguageRegistry>,
) {
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<LanguageRegistry>,
}
impl ExtensionGrammarProxy for LanguageServerRegistryProxy {
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
}
impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
fn register_language(
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
self.language_registry
.register_language(language, grammar, matcher, load);
}
fn remove_languages(
&self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
}

View File

@ -29,6 +29,7 @@ chrono.workspace = true
clap.workspace = true clap.workspace = true
client.workspace = true client.workspace = true
env_logger.workspace = true env_logger.workspace = true
extension.workspace = true
extension_host.workspace = true extension_host.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
@ -37,6 +38,7 @@ git_hosting_providers.workspace = true
gpui.workspace = true gpui.workspace = true
http_client.workspace = true http_client.workspace = true
language.workspace = true language.workspace = true
language_extension.workspace = true
languages.workspace = true languages.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true

View File

@ -1,4 +1,5 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use extension::ExtensionHostProxy;
use extension_host::headless_host::HeadlessExtensionStore; use extension_host::headless_host::HeadlessExtensionStore;
use fs::Fs; use fs::Fs;
use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel}; use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel};
@ -47,6 +48,7 @@ pub struct HeadlessAppState {
pub http_client: Arc<dyn HttpClient>, pub http_client: Arc<dyn HttpClient>,
pub node_runtime: NodeRuntime, pub node_runtime: NodeRuntime,
pub languages: Arc<LanguageRegistry>, pub languages: Arc<LanguageRegistry>,
pub extension_host_proxy: Arc<ExtensionHostProxy>,
} }
impl HeadlessProject { impl HeadlessProject {
@ -63,9 +65,11 @@ impl HeadlessProject {
http_client, http_client,
node_runtime, node_runtime,
languages, languages,
extension_host_proxy: proxy,
}: HeadlessAppState, }: HeadlessAppState,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
language_extension::init(proxy.clone(), languages.clone());
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
let worktree_store = cx.new_model(|cx| { let worktree_store = cx.new_model(|cx| {
@ -152,8 +156,8 @@ impl HeadlessProject {
let extensions = HeadlessExtensionStore::new( let extensions = HeadlessExtensionStore::new(
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
languages.clone(),
paths::remote_extensions_dir().to_path_buf(), paths::remote_extensions_dir().to_path_buf(),
proxy,
node_runtime, node_runtime,
cx, cx,
); );

View File

@ -1,6 +1,7 @@
use crate::headless_project::HeadlessProject; use crate::headless_project::HeadlessProject;
use client::{Client, UserStore}; use client::{Client, UserStore};
use clock::FakeSystemClock; use clock::FakeSystemClock;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs}; use fs::{FakeFs, Fs};
use gpui::{Context, Model, SemanticVersion, TestAppContext}; use gpui::{Context, Model, SemanticVersion, TestAppContext};
use http_client::{BlockedHttpClient, FakeHttpClient}; use http_client::{BlockedHttpClient, FakeHttpClient};
@ -1234,6 +1235,7 @@ pub async fn init_test(
let http_client = Arc::new(BlockedHttpClient); let http_client = Arc::new(BlockedHttpClient);
let node_runtime = NodeRuntime::unavailable(); let node_runtime = NodeRuntime::unavailable();
let languages = Arc::new(LanguageRegistry::new(cx.executor())); let languages = Arc::new(LanguageRegistry::new(cx.executor()));
let proxy = Arc::new(ExtensionHostProxy::new());
server_cx.update(HeadlessProject::init); server_cx.update(HeadlessProject::init);
let headless = server_cx.new_model(|cx| { let headless = server_cx.new_model(|cx| {
client::init_settings(cx); client::init_settings(cx);
@ -1245,6 +1247,7 @@ pub async fn init_test(
http_client, http_client,
node_runtime, node_runtime,
languages, languages,
extension_host_proxy: proxy,
}, },
cx, cx,
) )

View File

@ -3,6 +3,7 @@ use crate::HeadlessProject;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use chrono::Utc; use chrono::Utc;
use client::{telemetry, ProxySettings}; use client::{telemetry, ProxySettings};
use extension::ExtensionHostProxy;
use fs::{Fs, RealFs}; use fs::{Fs, RealFs};
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt}; 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); GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
git_hosting_providers::init(cx); git_hosting_providers::init(cx);
extension::init(cx);
let extension_host_proxy = ExtensionHostProxy::global(cx);
let project = cx.new_model(|cx| { let project = cx.new_model(|cx| {
let fs = Arc::new(RealFs::new(Default::default(), None)); let fs = Arc::new(RealFs::new(Default::default(), None));
let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx); let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx);
@ -466,6 +470,7 @@ pub fn execute_run(
http_client, http_client,
node_runtime, node_runtime,
languages, languages,
extension_host_proxy,
}, },
cx, cx,
) )

View File

@ -11,6 +11,7 @@ workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true
extension.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true

View File

@ -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<SnippetRegistry>,
}
impl ExtensionSnippetProxy for SnippetRegistryProxy {
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
self.snippet_registry
.register_snippets(path, snippet_contents)
}
}

View File

@ -1,3 +1,4 @@
mod extension_snippet;
mod format; mod format;
mod registry; mod registry;
@ -18,6 +19,7 @@ use util::ResultExt;
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
SnippetRegistry::init_global(cx); SnippetRegistry::init_global(cx);
extension_snippet::init(cx);
} }
// Is `None` if the snippet file is global. // Is `None` if the snippet file is global.

View File

@ -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

View File

@ -0,0 +1 @@
../../LICENSE-GPL

View File

@ -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<ExtensionHostProxy>,
theme_registry: Arc<ThemeRegistry>,
executor: BackgroundExecutor,
) {
extension_host_proxy.register_theme_proxy(ThemeRegistryProxy {
theme_registry,
executor,
});
}
struct ThemeRegistryProxy {
theme_registry: Arc<ThemeRegistry>,
executor: BackgroundExecutor,
}
impl ExtensionThemeProxy for ThemeRegistryProxy {
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
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<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
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)
}
}

View File

@ -19,7 +19,6 @@ activity_indicator.workspace = true
anyhow.workspace = true anyhow.workspace = true
assets.workspace = true assets.workspace = true
assistant.workspace = true assistant.workspace = true
assistant_slash_command.workspace = true
async-watch.workspace = true async-watch.workspace = true
audio.workspace = true audio.workspace = true
auto_update.workspace = true auto_update.workspace = true
@ -36,12 +35,12 @@ collab_ui.workspace = true
collections.workspace = true collections.workspace = true
command_palette.workspace = true command_palette.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
context_servers.workspace = true
copilot.workspace = true copilot.workspace = true
db.workspace = true db.workspace = true
diagnostics.workspace = true diagnostics.workspace = true
editor.workspace = true editor.workspace = true
env_logger.workspace = true env_logger.workspace = true
extension.workspace = true
extension_host.workspace = true extension_host.workspace = true
extensions_ui.workspace = true extensions_ui.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
@ -56,11 +55,11 @@ go_to_line.workspace = true
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] } gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
http_client.workspace = true http_client.workspace = true
image_viewer.workspace = true image_viewer.workspace = true
indexed_docs.workspace = true
inline_completion_button.workspace = true inline_completion_button.workspace = true
install_cli.workspace = true install_cli.workspace = true
journal.workspace = true journal.workspace = true
language.workspace = true language.workspace = true
language_extension.workspace = true
language_model.workspace = true language_model.workspace = true
language_models.workspace = true language_models.workspace = true
language_selector.workspace = true language_selector.workspace = true
@ -109,6 +108,7 @@ tasks_ui.workspace = true
telemetry_events.workspace = true telemetry_events.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
theme.workspace = true theme.workspace = true
theme_extension.workspace = true
theme_selector.workspace = true theme_selector.workspace = true
time.workspace = true time.workspace = true
toolchain_selector.workspace = true toolchain_selector.workspace = true

View File

@ -5,16 +5,15 @@ mod reliability;
mod zed; mod zed;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::Offset; use chrono::Offset;
use clap::{command, Parser}; use clap::{command, Parser};
use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{parse_zed_link, Client, ProxySettings, UserStore}; use client::{parse_zed_link, Client, ProxySettings, UserStore};
use collab_ui::channel_view::ChannelView; use collab_ui::channel_view::ChannelView;
use context_servers::ContextServerFactoryRegistry;
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE}; use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
use editor::Editor; use editor::Editor;
use env_logger::Builder; use env_logger::Builder;
use extension::ExtensionHostProxy;
use fs::{Fs, RealFs}; use fs::{Fs, RealFs};
use futures::{future, StreamExt}; use futures::{future, StreamExt};
use git::GitHostingProviderRegistry; use git::GitHostingProviderRegistry;
@ -23,7 +22,6 @@ use gpui::{
VisualContext, VisualContext,
}; };
use http_client::{read_proxy_from_env, Uri}; use http_client::{read_proxy_from_env, Uri};
use indexed_docs::IndexedDocsRegistry;
use language::LanguageRegistry; use language::LanguageRegistry;
use log::LevelFilter; use log::LevelFilter;
use reqwest_client::ReqwestClient; use reqwest_client::ReqwestClient;
@ -40,7 +38,6 @@ use settings::{
}; };
use simplelog::ConfigBuilder; use simplelog::ConfigBuilder;
use smol::process::Command; use smol::process::Command;
use snippet_provider::SnippetRegistry;
use std::{ use std::{
env, env,
fs::OpenOptions, fs::OpenOptions,
@ -284,6 +281,9 @@ fn main() {
OpenListener::set_global(cx, open_listener.clone()); OpenListener::set_global(cx, open_listener.clone());
extension::init(cx);
let extension_host_proxy = ExtensionHostProxy::global(cx);
let client = Client::production(cx); let client = Client::production(cx);
cx.set_http_client(client.http_client().clone()); cx.set_http_client(client.http_client().clone());
let mut languages = LanguageRegistry::new(cx.background_executor().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); let node_runtime = NodeRuntime::new(client.http_client(), rx);
language::init(cx); language::init(cx);
language_extension::init(extension_host_proxy.clone(), languages.clone());
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.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)); let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
@ -326,7 +327,6 @@ fn main() {
zed::init(cx); zed::init(cx);
project::Project::init(&client, cx); project::Project::init(&client, cx);
client::init(&client, cx); client::init(&client, cx);
language::init(cx);
let telemetry = client.telemetry(); let telemetry = client.telemetry();
telemetry.start( telemetry.start(
system_id.as_ref().map(|id| id.to_string()), system_id.as_ref().map(|id| id.to_string()),
@ -376,6 +376,11 @@ fn main() {
SystemAppearance::init(cx); SystemAppearance::init(cx);
theme::init(theme::LoadThemes::All(Box::new(Assets)), 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); command_palette::init(cx);
let copilot_language_server_id = app_state.languages.next_language_server_id(); let copilot_language_server_id = app_state.languages.next_language_server_id();
copilot::init( copilot::init(
@ -407,17 +412,8 @@ fn main() {
app_state.client.telemetry().clone(), app_state.client.telemetry().clone(),
cx, 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( extension_host::init(
api, extension_host_proxy,
app_state.fs.clone(), app_state.fs.clone(),
app_state.client.clone(), app_state.client.clone(),
app_state.node_runtime.clone(), app_state.node_runtime.clone(),