Extension refactor (#20305)
This contains the main changes to the extensions crate from #20049. The primary goal here is removing dependencies that we can't include on the remote. Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Marshall Bowers <elliott.codes@gmail.com> Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
f22e56ff42
commit
608addf641
30 changed files with 675 additions and 236 deletions
|
@ -14,7 +14,6 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
@ -25,7 +24,6 @@ fs.workspace = true
|
|||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
|
@ -39,16 +37,13 @@ serde.workspace = true
|
|||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
task.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmtime.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
|
@ -59,4 +54,3 @@ language = { workspace = true, features = ["test-support"] }
|
|||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
mod extension_indexed_docs_provider;
|
||||
mod extension_lsp_adapter;
|
||||
mod extension_settings;
|
||||
mod extension_slash_command;
|
||||
mod wasm_host;
|
||||
pub mod extension_lsp_adapter;
|
||||
pub mod extension_settings;
|
||||
pub mod wasm_host;
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
|
||||
use crate::extension_slash_command::ExtensionSlashCommand;
|
||||
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
|
||||
use collections::{btree_map, BTreeMap, HashSet};
|
||||
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||
use extension::SchemaVersion;
|
||||
pub use extension::ExtensionManifest;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
channel::{
|
||||
|
@ -28,14 +20,13 @@ use futures::{
|
|||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
|
||||
WeakModel,
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
|
||||
SharedString, Task, WeakModel,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
|
||||
LoadedLanguage, QUERY_FILENAME_PREFIXES,
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
|
@ -43,7 +34,6 @@ use release_channel::ReleaseChannel;
|
|||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
|
@ -52,20 +42,19 @@ use std::{
|
|||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use url::Url;
|
||||
use util::{maybe, ResultExt};
|
||||
use util::ResultExt;
|
||||
use wasm_host::{
|
||||
wit::{is_supported_wasm_api_version, wasm_api_version_range},
|
||||
WasmExtension, WasmHost,
|
||||
};
|
||||
|
||||
pub use extension::{
|
||||
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, OldExtensionManifest,
|
||||
ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,
|
||||
};
|
||||
pub use extension_settings::ExtensionSettings;
|
||||
|
||||
const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
|
||||
|
||||
/// The current extension [`SchemaVersion`] supported by Zed.
|
||||
|
@ -100,26 +89,98 @@ pub fn is_version_compatible(
|
|||
true
|
||||
}
|
||||
|
||||
pub trait DocsDatabase: Send + Sync + 'static {
|
||||
fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
|
||||
}
|
||||
|
||||
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, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
|
||||
|
||||
fn remove_lsp_adapter(
|
||||
&self,
|
||||
_language: &LanguageName,
|
||||
_server_name: &language::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,
|
||||
_slash_command: wit::SlashCommand,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_docs_provider(
|
||||
&self,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
_provider_id: Arc<str>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
_server_name: language::LanguageServerName,
|
||||
_status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
builder: Arc<ExtensionBuilder>,
|
||||
extension_index: ExtensionIndex,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
installed_dir: PathBuf,
|
||||
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
index_path: PathBuf,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
modified_extensions: HashSet<Arc<str>>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
tasks: Vec<Task<()>>,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub builder: Arc<ExtensionBuilder>,
|
||||
pub extension_index: ExtensionIndex,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub http_client: Arc<HttpClientWithUrl>,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
pub reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
pub reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
pub installed_dir: PathBuf,
|
||||
pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
pub index_path: PathBuf,
|
||||
pub modified_extensions: HashSet<Arc<str>>,
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
pub tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -158,26 +219,25 @@ pub struct ExtensionIndexEntry {
|
|||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexThemeEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexLanguageEntry {
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
matcher: LanguageMatcher,
|
||||
grammar: Option<Arc<str>>,
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
pub matcher: LanguageMatcher,
|
||||
pub grammar: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
ExtensionSettings::register(cx);
|
||||
|
@ -186,16 +246,12 @@ pub fn init(
|
|||
ExtensionStore::new(
|
||||
paths::extensions_dir().clone(),
|
||||
None,
|
||||
registration_hooks,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
client.http_client().clone(),
|
||||
Some(client.telemetry().clone()),
|
||||
node_runtime,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
SnippetRegistry::global(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -222,16 +278,12 @@ impl ExtensionStore {
|
|||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
build_dir: Option<PathBuf>,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
builder_client: Arc<dyn HttpClient>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let work_dir = extensions_dir.join("work");
|
||||
|
@ -241,6 +293,7 @@ impl ExtensionStore {
|
|||
|
||||
let (reload_tx, mut reload_rx) = unbounded();
|
||||
let mut this = Self {
|
||||
registration_hooks: extension_api.clone(),
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
|
@ -252,7 +305,7 @@ impl ExtensionStore {
|
|||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
extension_api,
|
||||
work_dir,
|
||||
cx,
|
||||
),
|
||||
|
@ -260,11 +313,6 @@ impl ExtensionStore {
|
|||
fs,
|
||||
http_client,
|
||||
telemetry,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
reload_tx,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
|
@ -325,6 +373,7 @@ impl ExtensionStore {
|
|||
async move {
|
||||
load_initial_extensions.await;
|
||||
|
||||
let mut index_changed = false;
|
||||
let mut debounce_timer = cx
|
||||
.background_executor()
|
||||
.spawn(futures::future::pending())
|
||||
|
@ -332,17 +381,21 @@ impl ExtensionStore {
|
|||
loop {
|
||||
select_biased! {
|
||||
_ = debounce_timer => {
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
if index_changed {
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
index_changed = false;
|
||||
}
|
||||
}
|
||||
extension_id = reload_rx.next() => {
|
||||
let Some(extension_id) = extension_id else { break; };
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.modified_extensions.extend(extension_id);
|
||||
})?;
|
||||
index_changed = true;
|
||||
debounce_timer = cx
|
||||
.background_executor()
|
||||
.timer(RELOAD_DEBOUNCE_DURATION)
|
||||
|
@ -386,7 +439,7 @@ impl ExtensionStore {
|
|||
this
|
||||
}
|
||||
|
||||
fn reload(
|
||||
pub fn reload(
|
||||
&mut self,
|
||||
modified_extension: Option<Arc<str>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
|
@ -1039,7 +1092,7 @@ 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.language_registry
|
||||
self.registration_hooks
|
||||
.remove_lsp_adapter(&language, language_server_name);
|
||||
}
|
||||
}
|
||||
|
@ -1047,8 +1100,8 @@ impl ExtensionStore {
|
|||
|
||||
self.wasm_extensions
|
||||
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
|
||||
self.theme_registry.remove_user_themes(&themes_to_remove);
|
||||
self.language_registry
|
||||
self.registration_hooks.remove_user_themes(themes_to_remove);
|
||||
self.registration_hooks
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
|
||||
let languages_to_add = new_index
|
||||
|
@ -1083,7 +1136,7 @@ impl ExtensionStore {
|
|||
}));
|
||||
}
|
||||
|
||||
self.language_registry
|
||||
self.registration_hooks
|
||||
.register_wasm_grammars(grammars_to_add);
|
||||
|
||||
for (language_name, language) in languages_to_add {
|
||||
|
@ -1092,11 +1145,11 @@ impl ExtensionStore {
|
|||
Path::new(language.extension.as_ref()),
|
||||
language.path.as_path(),
|
||||
]);
|
||||
self.language_registry.register_language(
|
||||
self.registration_hooks.register_language(
|
||||
language_name.clone(),
|
||||
language.grammar.clone(),
|
||||
language.matcher.clone(),
|
||||
move || {
|
||||
Arc::new(move || {
|
||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||
let queries = load_plugin_queries(&language_path);
|
||||
|
@ -1115,15 +1168,14 @@ impl ExtensionStore {
|
|||
context_provider,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let wasm_host = self.wasm_host.clone();
|
||||
let root_dir = self.installed_dir.clone();
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
let snippet_registry = self.snippet_registry.clone();
|
||||
let api = self.registration_hooks.clone();
|
||||
let extension_entries = extensions_to_load
|
||||
.iter()
|
||||
.filter_map(|name| new_index.extensions.get(name).cloned())
|
||||
|
@ -1138,18 +1190,14 @@ impl ExtensionStore {
|
|||
.spawn({
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for theme_path in &themes_to_add {
|
||||
theme_registry
|
||||
.load_user_theme(theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
for theme_path in themes_to_add.into_iter() {
|
||||
api.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()
|
||||
{
|
||||
snippet_registry
|
||||
.register_snippets(snippets_path, &snippets_contents)
|
||||
api.register_snippets(snippets_path, &snippets_contents)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
@ -1163,30 +1211,13 @@ impl ExtensionStore {
|
|||
continue;
|
||||
};
|
||||
|
||||
let wasm_extension = maybe!(async {
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([extension.manifest.clone().id.as_ref(), "extension.wasm"]);
|
||||
let mut wasm_file = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension.manifest.clone().clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to load wasm extension {}", extension.manifest.id)
|
||||
})
|
||||
})
|
||||
let extension_path = root_dir.join(extension.manifest.id.as_ref());
|
||||
let wasm_extension = WasmExtension::load(
|
||||
extension_path,
|
||||
&extension.manifest,
|
||||
wasm_host.clone(),
|
||||
&cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(wasm_extension) = wasm_extension.log_err() {
|
||||
|
@ -1205,9 +1236,9 @@ impl ExtensionStore {
|
|||
for (manifest, wasm_extension) in &wasm_extensions {
|
||||
for (language_server_id, language_server_config) in &manifest.language_servers {
|
||||
for language in language_server_config.languages() {
|
||||
this.language_registry.register_lsp_adapter(
|
||||
this.registration_hooks.register_lsp_adapter(
|
||||
language.clone(),
|
||||
Arc::new(ExtensionLspAdapter {
|
||||
ExtensionLspAdapter {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
language_server_id: language_server_id.clone(),
|
||||
|
@ -1215,43 +1246,38 @@ impl ExtensionStore {
|
|||
name: language_server_id.0.to_string(),
|
||||
language_name: language.to_string(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (slash_command_name, slash_command) in &manifest.slash_commands {
|
||||
this.slash_command_registry.register_command(
|
||||
ExtensionSlashCommand {
|
||||
command: crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
},
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
this.registration_hooks.register_slash_command(
|
||||
crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
},
|
||||
false,
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
for (provider_id, _provider) in &manifest.indexed_docs_providers {
|
||||
this.indexed_docs_registry.register_provider(Box::new(
|
||||
ExtensionIndexedDocsProvider {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
id: ProviderId(provider_id.clone()),
|
||||
},
|
||||
));
|
||||
this.registration_hooks.register_docs_provider(
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
provider_id.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
this.registration_hooks.reload_current_theme(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
|
@ -1262,6 +1288,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();
|
||||
cx.background_executor().spawn(async move {
|
||||
let start_time = Instant::now();
|
||||
let mut index = ExtensionIndex::default();
|
||||
|
@ -1283,9 +1310,14 @@ impl ExtensionStore {
|
|||
continue;
|
||||
}
|
||||
|
||||
Self::add_extension_to_index(fs.clone(), extension_dir, &mut index)
|
||||
.await
|
||||
.log_err();
|
||||
Self::add_extension_to_index(
|
||||
fs.clone(),
|
||||
extension_dir,
|
||||
&mut index,
|
||||
extension_api.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1305,6 +1337,7 @@ impl ExtensionStore {
|
|||
fs: Arc<dyn Fs>,
|
||||
extension_dir: PathBuf,
|
||||
index: &mut ExtensionIndex,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
) -> Result<()> {
|
||||
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
|
||||
let extension_id = extension_manifest.id.clone();
|
||||
|
@ -1356,7 +1389,8 @@ impl ExtensionStore {
|
|||
continue;
|
||||
};
|
||||
|
||||
let Some(theme_family) = theme::read_user_theme(&theme_path, fs.clone())
|
||||
let Some(theme_families) = extension_api
|
||||
.list_theme_names(theme_path.clone(), fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
else {
|
||||
|
@ -1368,9 +1402,9 @@ impl ExtensionStore {
|
|||
extension_manifest.themes.push(relative_path.clone());
|
||||
}
|
||||
|
||||
for theme in theme_family.themes {
|
||||
for theme_name in theme_families {
|
||||
index.themes.insert(
|
||||
theme.name.into(),
|
||||
theme_name.into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: extension_id.clone(),
|
||||
path: relative_path.clone(),
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::FutureExt;
|
||||
use indexed_docs::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
|
||||
use wasmtime_wasi::WasiView;
|
||||
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
pub struct ExtensionIndexedDocsProvider {
|
||||
pub(crate) extension: WasmExtension,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub(crate) id: ProviderId,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
|
||||
fn id(&self) -> ProviderId {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn database_path(&self) -> PathBuf {
|
||||
let mut database_path = self.host.work_dir.clone();
|
||||
database_path.push(self.extension.manifest.id.as_ref());
|
||||
database_path.push("docs");
|
||||
database_path.push(format!("{}.0.mdb", self.id));
|
||||
|
||||
database_path
|
||||
}
|
||||
|
||||
async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
|
||||
self.extension
|
||||
.call({
|
||||
let id = self.id.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let packages = extension
|
||||
.call_suggest_docs_packages(store, id.as_ref())
|
||||
.await?
|
||||
.map_err(|err| anyhow!("{err:?}"))?;
|
||||
|
||||
Ok(packages
|
||||
.into_iter()
|
||||
.map(|package| PackageName::from(package.as_str()))
|
||||
.collect())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
|
||||
self.extension
|
||||
.call({
|
||||
let id = self.id.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let database_resource = store.data_mut().table().push(database)?;
|
||||
extension
|
||||
.call_index_docs(
|
||||
store,
|
||||
id.as_ref(),
|
||||
package.as_ref(),
|
||||
database_resource,
|
||||
)
|
||||
.await?
|
||||
.map_err(|err| anyhow!("{err:?}"))?;
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use wasmtime_wasi::WasiView;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
pub struct ExtensionSlashCommand {
|
||||
pub(crate) extension: WasmExtension,
|
||||
#[allow(unused)]
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub(crate) command: crate::wit::SlashCommand,
|
||||
}
|
||||
|
||||
impl SlashCommand for ExtensionSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
self.command.name.clone()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
self.command.description.clone()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.command.tooltip_text.clone()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.command.requires_argument
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
let arguments = arguments.to_owned();
|
||||
cx.background_executor().spawn(async move {
|
||||
self.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
move |extension, store| {
|
||||
async move {
|
||||
let completions = extension
|
||||
.call_complete_slash_command_argument(
|
||||
store,
|
||||
&this.command,
|
||||
&arguments,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
|
||||
anyhow::Ok(
|
||||
completions
|
||||
.into_iter()
|
||||
.map(|completion| ArgumentCompletion {
|
||||
label: completion.label.into(),
|
||||
new_text: completion.new_text,
|
||||
replace_previous_arguments: false,
|
||||
after_completion: completion.run_command.into(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
_workspace: WeakView<Workspace>,
|
||||
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let arguments = arguments.to_owned();
|
||||
let output = cx.background_executor().spawn(async move {
|
||||
self.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
move |extension, store| {
|
||||
async move {
|
||||
let resource = if let Some(delegate) = delegate {
|
||||
Some(store.data_mut().table().push(delegate)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let output = extension
|
||||
.call_run_slash_command(store, &this.command, &arguments, resource)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
|
||||
anyhow::Ok(output)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await
|
||||
});
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let output = output.await?;
|
||||
Ok(SlashCommandOutput {
|
||||
text: output.text,
|
||||
sections: output
|
||||
.sections
|
||||
.into_iter()
|
||||
.map(|section| SlashCommandOutputSection {
|
||||
range: section.range.into(),
|
||||
icon: IconName::Code,
|
||||
label: section.label.into(),
|
||||
metadata: None,
|
||||
})
|
||||
.collect(),
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,783 +0,0 @@
|
|||
use crate::extension_settings::ExtensionSettings;
|
||||
use crate::{
|
||||
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
|
||||
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
|
||||
RELOAD_DEBOUNCE_DURATION,
|
||||
};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use extension::SchemaVersion;
|
||||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
use http_client::{FakeHttpClient, Response};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
|
||||
use release_channel::AppVersion;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use serde_json::json;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ThemeRegistry;
|
||||
use util::test::temp_tree;
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let http_client = FakeHttpClient::with_200_response();
|
||||
|
||||
fs.insert_tree(
|
||||
"/the-extension-dir",
|
||||
json!({
|
||||
"installed": {
|
||||
"zed-monokai": {
|
||||
"extension.json": r#"{
|
||||
"id": "zed-monokai",
|
||||
"name": "Zed Monokai",
|
||||
"version": "2.0.0",
|
||||
"themes": {
|
||||
"Monokai Dark": "themes/monokai.json",
|
||||
"Monokai Light": "themes/monokai.json",
|
||||
"Monokai Pro Dark": "themes/monokai-pro.json",
|
||||
"Monokai Pro Light": "themes/monokai-pro.json"
|
||||
}
|
||||
}"#,
|
||||
"themes": {
|
||||
"monokai.json": r#"{
|
||||
"name": "Monokai",
|
||||
"author": "Someone",
|
||||
"themes": [
|
||||
{
|
||||
"name": "Monokai Dark",
|
||||
"appearance": "dark",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"name": "Monokai Light",
|
||||
"appearance": "light",
|
||||
"style": {}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
"monokai-pro.json": r#"{
|
||||
"name": "Monokai Pro",
|
||||
"author": "Someone",
|
||||
"themes": [
|
||||
{
|
||||
"name": "Monokai Pro Dark",
|
||||
"appearance": "dark",
|
||||
"style": {}
|
||||
},
|
||||
{
|
||||
"name": "Monokai Pro Light",
|
||||
"appearance": "light",
|
||||
"style": {}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
}
|
||||
},
|
||||
"zed-ruby": {
|
||||
"extension.json": r#"{
|
||||
"id": "zed-ruby",
|
||||
"name": "Zed Ruby",
|
||||
"version": "1.0.0",
|
||||
"grammars": {
|
||||
"ruby": "grammars/ruby.wasm",
|
||||
"embedded_template": "grammars/embedded_template.wasm"
|
||||
},
|
||||
"languages": {
|
||||
"ruby": "languages/ruby",
|
||||
"erb": "languages/erb"
|
||||
}
|
||||
}"#,
|
||||
"grammars": {
|
||||
"ruby.wasm": "",
|
||||
"embedded_template.wasm": "",
|
||||
},
|
||||
"languages": {
|
||||
"ruby": {
|
||||
"config.toml": r#"
|
||||
name = "Ruby"
|
||||
grammar = "ruby"
|
||||
path_suffixes = ["rb"]
|
||||
"#,
|
||||
"highlights.scm": "",
|
||||
},
|
||||
"erb": {
|
||||
"config.toml": r#"
|
||||
name = "ERB"
|
||||
grammar = "embedded_template"
|
||||
path_suffixes = ["erb"]
|
||||
"#,
|
||||
"highlights.scm": "",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut expected_index = ExtensionIndex {
|
||||
extensions: [
|
||||
(
|
||||
"zed-ruby".into(),
|
||||
ExtensionIndexEntry {
|
||||
manifest: Arc::new(ExtensionManifest {
|
||||
id: "zed-ruby".into(),
|
||||
name: "Zed Ruby".into(),
|
||||
version: "1.0.0".into(),
|
||||
schema_version: SchemaVersion::ZERO,
|
||||
description: None,
|
||||
authors: Vec::new(),
|
||||
repository: None,
|
||||
themes: Default::default(),
|
||||
lib: Default::default(),
|
||||
languages: vec!["languages/erb".into(), "languages/ruby".into()],
|
||||
grammars: [
|
||||
("embedded_template".into(), GrammarManifestEntry::default()),
|
||||
("ruby".into(), GrammarManifestEntry::default()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
snippets: None,
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"zed-monokai".into(),
|
||||
ExtensionIndexEntry {
|
||||
manifest: Arc::new(ExtensionManifest {
|
||||
id: "zed-monokai".into(),
|
||||
name: "Zed Monokai".into(),
|
||||
version: "2.0.0".into(),
|
||||
schema_version: SchemaVersion::ZERO,
|
||||
description: None,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
themes: vec![
|
||||
"themes/monokai-pro.json".into(),
|
||||
"themes/monokai.json".into(),
|
||||
],
|
||||
lib: Default::default(),
|
||||
languages: Default::default(),
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
snippets: None,
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
languages: [
|
||||
(
|
||||
"ERB".into(),
|
||||
ExtensionIndexLanguageEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "languages/erb".into(),
|
||||
grammar: Some("embedded_template".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["erb".into()],
|
||||
first_line_pattern: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
"Ruby".into(),
|
||||
ExtensionIndexLanguageEntry {
|
||||
extension: "zed-ruby".into(),
|
||||
path: "languages/ruby".into(),
|
||||
grammar: Some("ruby".into()),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rb".into()],
|
||||
first_line_pattern: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
themes: [
|
||||
(
|
||||
"Monokai Dark".into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Light".into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Pro Dark".into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai-pro.json".into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Monokai Pro Light".into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: "zed-monokai".into(),
|
||||
path: "themes/monokai-pro.json".into(),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let slash_command_registry = SlashCommandRegistry::new();
|
||||
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
|
||||
let snippet_registry = Arc::new(SnippetRegistry::new());
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry.clone(),
|
||||
indexed_docs_registry.clone(),
|
||||
snippet_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(super::RELOAD_DEBOUNCE_DURATION);
|
||||
store.read_with(cx, |store, _| {
|
||||
let index = &store.extension_index;
|
||||
assert_eq!(index.extensions, expected_index.extensions);
|
||||
assert_eq!(index.languages, expected_index.languages);
|
||||
assert_eq!(index.themes, expected_index.themes);
|
||||
|
||||
assert_eq!(
|
||||
language_registry.language_names(),
|
||||
["ERB", "Plain Text", "Ruby"]
|
||||
);
|
||||
assert_eq!(
|
||||
theme_registry.list_names(),
|
||||
[
|
||||
"Monokai Dark",
|
||||
"Monokai Light",
|
||||
"Monokai Pro Dark",
|
||||
"Monokai Pro Light",
|
||||
"One Dark",
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
fs.insert_tree(
|
||||
"/the-extension-dir/installed/zed-gruvbox",
|
||||
json!({
|
||||
"extension.json": r#"{
|
||||
"id": "zed-gruvbox",
|
||||
"name": "Zed Gruvbox",
|
||||
"version": "1.0.0",
|
||||
"themes": {
|
||||
"Gruvbox": "themes/gruvbox.json"
|
||||
}
|
||||
}"#,
|
||||
"themes": {
|
||||
"gruvbox.json": r#"{
|
||||
"name": "Gruvbox",
|
||||
"author": "Someone Else",
|
||||
"themes": [
|
||||
{
|
||||
"name": "Gruvbox",
|
||||
"appearance": "dark",
|
||||
"style": {}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
expected_index.extensions.insert(
|
||||
"zed-gruvbox".into(),
|
||||
ExtensionIndexEntry {
|
||||
manifest: Arc::new(ExtensionManifest {
|
||||
id: "zed-gruvbox".into(),
|
||||
name: "Zed Gruvbox".into(),
|
||||
version: "1.0.0".into(),
|
||||
schema_version: SchemaVersion::ZERO,
|
||||
description: None,
|
||||
authors: vec![],
|
||||
repository: None,
|
||||
themes: vec!["themes/gruvbox.json".into()],
|
||||
lib: Default::default(),
|
||||
languages: Default::default(),
|
||||
grammars: BTreeMap::default(),
|
||||
language_servers: BTreeMap::default(),
|
||||
slash_commands: BTreeMap::default(),
|
||||
indexed_docs_providers: BTreeMap::default(),
|
||||
snippets: None,
|
||||
}),
|
||||
dev: false,
|
||||
},
|
||||
);
|
||||
expected_index.themes.insert(
|
||||
"Gruvbox".into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: "zed-gruvbox".into(),
|
||||
path: "themes/gruvbox.json".into(),
|
||||
},
|
||||
);
|
||||
|
||||
#[allow(clippy::let_underscore_future)]
|
||||
let _ = store.update(cx, |store, cx| store.reload(None, cx));
|
||||
|
||||
cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
store.read_with(cx, |store, _| {
|
||||
let index = &store.extension_index;
|
||||
assert_eq!(index.extensions, expected_index.extensions);
|
||||
assert_eq!(index.languages, expected_index.languages);
|
||||
assert_eq!(index.themes, expected_index.themes);
|
||||
|
||||
assert_eq!(
|
||||
theme_registry.list_names(),
|
||||
[
|
||||
"Gruvbox",
|
||||
"Monokai Dark",
|
||||
"Monokai Light",
|
||||
"Monokai Pro Dark",
|
||||
"Monokai Pro Light",
|
||||
"One Dark",
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
let prev_fs_metadata_call_count = fs.metadata_call_count();
|
||||
let prev_fs_read_dir_call_count = fs.read_dir_call_count();
|
||||
|
||||
// Create new extension store, as if Zed were restarting.
|
||||
drop(store);
|
||||
let store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
store.read_with(cx, |store, _| {
|
||||
assert_eq!(store.extension_index, expected_index);
|
||||
assert_eq!(
|
||||
language_registry.language_names(),
|
||||
["ERB", "Plain Text", "Ruby"]
|
||||
);
|
||||
assert_eq!(
|
||||
language_registry.grammar_names(),
|
||||
["embedded_template".into(), "ruby".into()]
|
||||
);
|
||||
assert_eq!(
|
||||
theme_registry.list_names(),
|
||||
[
|
||||
"Gruvbox",
|
||||
"Monokai Dark",
|
||||
"Monokai Light",
|
||||
"Monokai Pro Dark",
|
||||
"Monokai Pro Light",
|
||||
"One Dark",
|
||||
]
|
||||
);
|
||||
|
||||
// The on-disk manifest limits the number of FS calls that need to be made
|
||||
// on startup.
|
||||
assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
|
||||
assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
|
||||
});
|
||||
|
||||
store.update(cx, |store, cx| {
|
||||
store.uninstall_extension("zed-ruby".into(), cx)
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
expected_index.extensions.remove("zed-ruby");
|
||||
expected_index.languages.remove("Ruby");
|
||||
expected_index.languages.remove("ERB");
|
||||
|
||||
store.read_with(cx, |store, _| {
|
||||
assert_eq!(store.extension_index, expected_index);
|
||||
assert_eq!(language_registry.language_names(), ["Plain Text"]);
|
||||
assert_eq!(language_registry.grammar_names(), []);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let root_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap();
|
||||
let cache_dir = root_dir.join("target");
|
||||
let test_extension_id = "test-extension";
|
||||
let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
|
||||
|
||||
let fs = Arc::new(RealFs::default());
|
||||
let extensions_dir = temp_tree(json!({
|
||||
"installed": {},
|
||||
"work": {}
|
||||
}));
|
||||
let project_dir = temp_tree(json!({
|
||||
"test.gleam": ""
|
||||
}));
|
||||
|
||||
let extensions_dir = extensions_dir.path().canonicalize().unwrap();
|
||||
let project_dir = project_dir.path().canonicalize().unwrap();
|
||||
|
||||
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 theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
|
||||
let slash_command_registry = SlashCommandRegistry::new();
|
||||
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
|
||||
let snippet_registry = Arc::new(SnippetRegistry::new());
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let mut status_updates = language_registry.language_server_binary_statuses();
|
||||
|
||||
struct FakeLanguageServerVersion {
|
||||
version: String,
|
||||
binary_contents: String,
|
||||
http_request_count: usize,
|
||||
}
|
||||
|
||||
let language_server_version = Arc::new(Mutex::new(FakeLanguageServerVersion {
|
||||
version: "v1.2.3".into(),
|
||||
binary_contents: "the-binary-contents".into(),
|
||||
http_request_count: 0,
|
||||
}));
|
||||
|
||||
let extension_client = FakeHttpClient::create({
|
||||
let language_server_version = language_server_version.clone();
|
||||
move |request| {
|
||||
let language_server_version = language_server_version.clone();
|
||||
async move {
|
||||
let version = language_server_version.lock().version.clone();
|
||||
let binary_contents = language_server_version.lock().binary_contents.clone();
|
||||
|
||||
let github_releases_uri = "https://api.github.com/repos/gleam-lang/gleam/releases";
|
||||
let asset_download_uri =
|
||||
format!("https://fake-download.example.com/gleam-{version}");
|
||||
|
||||
let uri = request.uri().to_string();
|
||||
if uri == github_releases_uri {
|
||||
language_server_version.lock().http_request_count += 1;
|
||||
Ok(Response::new(
|
||||
json!([
|
||||
{
|
||||
"tag_name": version,
|
||||
"prerelease": false,
|
||||
"tarball_url": "",
|
||||
"zipball_url": "",
|
||||
"assets": [
|
||||
{
|
||||
"name": format!("gleam-{version}-aarch64-apple-darwin.tar.gz"),
|
||||
"browser_download_url": asset_download_uri
|
||||
},
|
||||
{
|
||||
"name": format!("gleam-{version}-x86_64-unknown-linux-musl.tar.gz"),
|
||||
"browser_download_url": asset_download_uri
|
||||
},
|
||||
{
|
||||
"name": format!("gleam-{version}-aarch64-unknown-linux-musl.tar.gz"),
|
||||
"browser_download_url": asset_download_uri
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
.to_string()
|
||||
.into(),
|
||||
))
|
||||
} else if uri == asset_download_uri {
|
||||
language_server_version.lock().http_request_count += 1;
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
let mut archive = async_tar::Builder::new(&mut bytes);
|
||||
let mut header = async_tar::Header::new_gnu();
|
||||
header.set_size(binary_contents.len() as u64);
|
||||
archive
|
||||
.append_data(&mut header, "gleam", binary_contents.as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
archive.into_inner().await.unwrap();
|
||||
let mut gzipped_bytes = Vec::new();
|
||||
let mut encoder = GzipEncoder::new(BufReader::new(bytes.as_slice()));
|
||||
encoder.read_to_end(&mut gzipped_bytes).await.unwrap();
|
||||
Ok(Response::new(gzipped_bytes.into()))
|
||||
} else {
|
||||
Ok(Response::builder().status(404).body("not found".into())?)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let user_agent = cx.update(|cx| {
|
||||
format!(
|
||||
"Zed/{} ({}; {})",
|
||||
AppVersion::global(cx),
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
)
|
||||
});
|
||||
let builder_client =
|
||||
Arc::new(ReqwestClient::user_agent(&user_agent).expect("Could not create HTTP client"));
|
||||
|
||||
let extension_store = cx.new_model(|cx| {
|
||||
ExtensionStore::new(
|
||||
extensions_dir.clone(),
|
||||
Some(cache_dir),
|
||||
fs.clone(),
|
||||
extension_client.clone(),
|
||||
builder_client,
|
||||
None,
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Ensure that debounces fire.
|
||||
let mut events = cx.events(&extension_store);
|
||||
let executor = cx.executor();
|
||||
let _task = cx.executor().spawn(async move {
|
||||
while let Some(event) = events.next().await {
|
||||
if let crate::Event::StartedReloading = event {
|
||||
executor.advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
extension_store.update(cx, |_, cx| {
|
||||
cx.subscribe(&extension_store, |_, _, event, _| {
|
||||
if matches!(event, Event::ExtensionFailedToLoad(_)) {
|
||||
panic!("extension failed to load");
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
extension_store
|
||||
.update(cx, |store, cx| {
|
||||
store.install_dev_extension(test_extension_dir.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut fake_servers = language_registry.register_fake_language_server(
|
||||
LanguageServerName("gleam".into()),
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(Default::default()),
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(project_dir.join("test.gleam"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let expected_server_path =
|
||||
extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
|
||||
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
|
||||
|
||||
assert_eq!(fake_server.binary.path, expected_server_path);
|
||||
assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
|
||||
assert_eq!(
|
||||
fs.load(&expected_server_path).await.unwrap(),
|
||||
expected_binary_contents
|
||||
);
|
||||
assert_eq!(language_server_version.lock().http_request_count, 2);
|
||||
assert_eq!(
|
||||
[
|
||||
status_updates.next().await.unwrap(),
|
||||
status_updates.next().await.unwrap(),
|
||||
status_updates.next().await.unwrap(),
|
||||
],
|
||||
[
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
),
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
),
|
||||
(
|
||||
LanguageServerName("gleam".into()),
|
||||
LanguageServerBinaryStatus::None
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// The extension creates custom labels for completion items.
|
||||
fake_server.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "foo".into(),
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
detail: Some("fn() -> Result(Nil, Error)".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "bar.baz".into(),
|
||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||
detail: Some("fn(List(a)) -> a".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "Quux".into(),
|
||||
kind: Some(lsp::CompletionItemKind::CONSTRUCTOR),
|
||||
detail: Some("fn(String) -> T".into()),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "my_string".into(),
|
||||
kind: Some(lsp::CompletionItemKind::CONSTANT),
|
||||
detail: Some("String".into()),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
let completion_labels = project
|
||||
.update(cx, |project, cx| {
|
||||
project.completions(&buffer, 0, DEFAULT_COMPLETION_CONTEXT, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|c| c.label.text)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
completion_labels,
|
||||
[
|
||||
"foo: fn() -> Result(Nil, Error)".to_string(),
|
||||
"bar.baz: fn(List(a)) -> a".to_string(),
|
||||
"Quux: fn(String) -> T".to_string(),
|
||||
"my_string: String".to_string(),
|
||||
]
|
||||
);
|
||||
|
||||
// Simulate a new version of the language server being released
|
||||
language_server_version.lock().version = "v2.0.0".into();
|
||||
language_server_version.lock().binary_contents = "the-new-binary-contents".into();
|
||||
language_server_version.lock().http_request_count = 0;
|
||||
|
||||
// Start a new instance of the language server.
|
||||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers([buffer.clone()], cx)
|
||||
});
|
||||
|
||||
// The extension has cached the binary path, and does not attempt
|
||||
// to reinstall it.
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
assert_eq!(fake_server.binary.path, expected_server_path);
|
||||
assert_eq!(
|
||||
fs.load(&expected_server_path).await.unwrap(),
|
||||
expected_binary_contents
|
||||
);
|
||||
assert_eq!(language_server_version.lock().http_request_count, 0);
|
||||
|
||||
// Reload the extension, clearing its cache.
|
||||
// Start a new instance of the language server.
|
||||
extension_store
|
||||
.update(cx, |store, cx| store.reload(Some("gleam".into()), cx))
|
||||
.await;
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers([buffer.clone()], cx)
|
||||
});
|
||||
|
||||
// The extension re-fetches the latest version of the language server.
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let new_expected_server_path =
|
||||
extensions_dir.join(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
|
||||
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
|
||||
assert_eq!(fake_server.binary.path, new_expected_server_path);
|
||||
assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
|
||||
assert_eq!(
|
||||
fs.load(&new_expected_server_path).await.unwrap(),
|
||||
expected_binary_contents
|
||||
);
|
||||
|
||||
// The old language server directory has been cleaned up.
|
||||
assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
Project::init_settings(cx);
|
||||
ExtensionSettings::register(cx);
|
||||
language::init(cx);
|
||||
});
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
pub(crate) mod wit;
|
||||
pub mod wit;
|
||||
|
||||
use crate::ExtensionManifest;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use crate::{ExtensionManifest, ExtensionRegistrationHooks};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use fs::{normalize_path, Fs};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{
|
||||
|
@ -14,7 +14,6 @@ use futures::{
|
|||
};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::NodeRuntime;
|
||||
use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
|
@ -28,15 +27,16 @@ use wasmtime::{
|
|||
};
|
||||
use wasmtime_wasi as wasi;
|
||||
use wit::Extension;
|
||||
pub use wit::SlashCommand;
|
||||
|
||||
pub(crate) struct WasmHost {
|
||||
pub struct WasmHost {
|
||||
engine: Engine,
|
||||
release_channel: ReleaseChannel,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
pub(crate) language_registry: Arc<LanguageRegistry>,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
pub work_dir: PathBuf,
|
||||
_main_thread_message_task: Task<()>,
|
||||
main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,
|
||||
}
|
||||
|
@ -44,16 +44,16 @@ pub(crate) struct WasmHost {
|
|||
#[derive(Clone)]
|
||||
pub struct WasmExtension {
|
||||
tx: UnboundedSender<ExtensionCall>,
|
||||
pub(crate) manifest: Arc<ExtensionManifest>,
|
||||
pub manifest: Arc<ExtensionManifest>,
|
||||
#[allow(unused)]
|
||||
pub zed_api_version: SemanticVersion,
|
||||
}
|
||||
|
||||
pub(crate) struct WasmState {
|
||||
pub struct WasmState {
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
pub(crate) table: ResourceTable,
|
||||
pub table: ResourceTable,
|
||||
ctx: wasi::WasiCtx,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub host: Arc<WasmHost>,
|
||||
}
|
||||
|
||||
type MainThreadCall =
|
||||
|
@ -81,7 +81,7 @@ impl WasmHost {
|
|||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
work_dir: PathBuf,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
|
@ -97,7 +97,7 @@ impl WasmHost {
|
|||
work_dir,
|
||||
http_client,
|
||||
node_runtime,
|
||||
language_registry,
|
||||
registration_hooks,
|
||||
release_channel: ReleaseChannel::global(cx),
|
||||
_main_thread_message_task: task,
|
||||
main_thread_message_tx: tx,
|
||||
|
@ -107,13 +107,13 @@ impl WasmHost {
|
|||
pub fn load_extension(
|
||||
self: &Arc<Self>,
|
||||
wasm_bytes: Vec<u8>,
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Task<Result<WasmExtension>> {
|
||||
let this = self.clone();
|
||||
let manifest = manifest.clone();
|
||||
executor.clone().spawn(async move {
|
||||
let zed_api_version =
|
||||
extension::parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
|
||||
let zed_api_version = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
|
||||
|
||||
let component = Component::from_binary(&this.engine, &wasm_bytes)
|
||||
.context("failed to compile wasm component")?;
|
||||
|
@ -151,7 +151,7 @@ impl WasmHost {
|
|||
.detach();
|
||||
|
||||
Ok(WasmExtension {
|
||||
manifest,
|
||||
manifest: manifest.clone(),
|
||||
tx,
|
||||
zed_api_version,
|
||||
})
|
||||
|
@ -198,7 +198,75 @@ impl WasmHost {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_wasm_extension_version(
|
||||
extension_id: &str,
|
||||
wasm_bytes: &[u8],
|
||||
) -> Result<SemanticVersion> {
|
||||
let mut version = None;
|
||||
|
||||
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
|
||||
if let wasmparser::Payload::CustomSection(s) =
|
||||
part.context("error parsing wasm extension")?
|
||||
{
|
||||
if s.name() == "zed:api-version" {
|
||||
version = parse_wasm_extension_version_custom_section(s.data());
|
||||
if version.is_none() {
|
||||
bail!(
|
||||
"extension {} has invalid zed:api-version section: {:?}",
|
||||
extension_id,
|
||||
s.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
|
||||
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
|
||||
//
|
||||
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
|
||||
// earlier as an `Err` rather than as a panic.
|
||||
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
|
||||
}
|
||||
|
||||
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
|
||||
if data.len() == 6 {
|
||||
Some(SemanticVersion::new(
|
||||
u16::from_be_bytes([data[0], data[1]]) as _,
|
||||
u16::from_be_bytes([data[2], data[3]]) as _,
|
||||
u16::from_be_bytes([data[4], data[5]]) as _,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmExtension {
|
||||
pub async fn load(
|
||||
extension_dir: PathBuf,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let path = extension_dir.join("extension.wasm");
|
||||
|
||||
let mut wasm_file = wasm_host
|
||||
.fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(wasm_bytes, manifest, cx.background_executor().clone())
|
||||
.await
|
||||
.with_context(|| format!("failed to load wasm extension {}", manifest.id))
|
||||
}
|
||||
|
||||
pub async fn call<T, Fn>(&self, f: Fn) -> T
|
||||
where
|
||||
T: 'static + Send,
|
||||
|
|
|
@ -3,10 +3,12 @@ mod since_v0_0_4;
|
|||
mod since_v0_0_6;
|
||||
mod since_v0_1_0;
|
||||
mod since_v0_2_0;
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
// use indexed_docs::IndexedDocsDatabase;
|
||||
use release_channel::ReleaseChannel;
|
||||
use since_v0_2_0 as latest;
|
||||
|
||||
use crate::DocsDatabase;
|
||||
|
||||
use super::{wasm_engine, WasmState};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use language::{LanguageServerName, LspAdapterDelegate};
|
||||
|
@ -394,7 +396,7 @@ impl Extension {
|
|||
store: &mut Store<WasmState>,
|
||||
provider: &str,
|
||||
package_name: &str,
|
||||
database: Resource<Arc<IndexedDocsDatabase>>,
|
||||
database: Resource<Arc<dyn DocsDatabase>>,
|
||||
) -> Result<Result<(), String>> {
|
||||
match self {
|
||||
Extension::V020(ext) => {
|
||||
|
|
|
@ -148,7 +148,7 @@ impl ExtensionImports for WasmState {
|
|||
};
|
||||
|
||||
self.host
|
||||
.language_registry
|
||||
.registration_hooks
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use crate::DocsDatabase;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
|
@ -7,7 +8,6 @@ use async_tar::Archive;
|
|||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ mod settings {
|
|||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
|
@ -512,7 +512,7 @@ impl ExtensionImports for WasmState {
|
|||
};
|
||||
|
||||
self.host
|
||||
.language_registry
|
||||
.registration_hooks
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use crate::DocsDatabase;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
|
@ -7,7 +8,6 @@ use async_tar::Archive;
|
|||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ mod settings {
|
|||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
|
@ -459,7 +459,7 @@ impl ExtensionImports for WasmState {
|
|||
};
|
||||
|
||||
self.host
|
||||
.language_registry
|
||||
.registration_hooks
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue