Detailed changes
@@ -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",
@@ -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" }
@@ -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);
@@ -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)]
@@ -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<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`].
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
@@ -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"] }
@@ -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,
)
@@ -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
@@ -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);
@@ -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()),
+ })
+ })
+ }
+ }),
+ )
+ });
+ }
+}
@@ -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
@@ -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;
@@ -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)
+ }
+}
@@ -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
@@ -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<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 registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
+ pub proxy: Arc<ExtensionHostProxy>,
pub builder: Arc<ExtensionBuilder>,
pub extension_index: ExtensionIndex,
pub fs: Arc<dyn Fs>,
@@ -240,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry {
actions!(zed, [ReloadExtensions]);
pub fn init(
- registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
+ extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
client: Arc<Client>,
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<PathBuf>,
- extension_api: Arc<dyn ExtensionRegistrationHooks>,
+ extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
http_client: Arc<HttpClientWithUrl>,
builder_client: Arc<dyn HttpClient>,
@@ -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<dyn Fs>,
extension_dir: PathBuf,
index: &mut ExtensionIndex,
- extension_api: Arc<dyn ExtensionRegistrationHooks>,
+ proxy: Arc<ExtensionHostProxy>,
) -> 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()
@@ -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<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)]
#[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,
@@ -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<dyn ExtensionRegistrationHooks>,
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)>>,
}
-#[derive(Clone, Debug)]
-pub struct ExtensionVersion {
- pub id: String,
- pub version: String,
- pub dev: bool,
-}
-
impl HeadlessExtensionStore {
pub fn new(
fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>,
- languages: Arc<LanguageRegistry>,
extension_dir: PathBuf,
+ extension_host_proxy: Arc<ExtensionHostProxy>,
node_runtime: NodeRuntime,
cx: &mut AppContext,
) -> Model<Self> {
- 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<Self>,
) -> Task<Result<()>> {
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<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)
- }
-}
@@ -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<dyn HttpClient>,
node_runtime: NodeRuntime,
- pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
+ pub(crate) proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
pub work_dir: PathBuf,
_main_thread_message_task: Task<()>,
@@ -330,7 +330,7 @@ impl WasmHost {
fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime,
- registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
+ proxy: Arc<ExtensionHostProxy>,
work_dir: PathBuf,
cx: &mut AppContext,
) -> Arc<Self> {
@@ -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,
@@ -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(())
}
@@ -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(())
}
@@ -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(())
}
@@ -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
@@ -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())
- })
- }
-}
@@ -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;
@@ -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<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 {
extension: Arc<dyn Extension>,
@@ -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);
+}
@@ -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()))),
@@ -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
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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<dyn LspAdapterDelegate>);
+struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
#[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<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>,
language_server_id: LanguageServerName,
language_name: LanguageName,
}
impl ExtensionLspAdapter {
- pub fn new(
+ fn new(
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language_name: LanguageName,
@@ -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);
+ }
+}
@@ -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
@@ -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<dyn HttpClient>,
pub node_runtime: NodeRuntime,
pub languages: Arc<LanguageRegistry>,
+ pub extension_host_proxy: Arc<ExtensionHostProxy>,
}
impl HeadlessProject {
@@ -63,9 +65,11 @@ impl HeadlessProject {
http_client,
node_runtime,
languages,
+ extension_host_proxy: proxy,
}: HeadlessAppState,
cx: &mut ModelContext<Self>,
) -> 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,
);
@@ -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,
)
@@ -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,
)
@@ -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
@@ -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)
+ }
+}
@@ -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.
@@ -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
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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)
+ }
+}
@@ -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
@@ -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(),