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

View file

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