pub mod wit; use crate::ExtensionManifest;use anyhow::{Context as _, Result, anyhow, bail};use async_trait::async_trait;use extension::{ CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary, DebugTaskDefinition, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,};use fs::{Fs, normalize_path};use futures::future::LocalBoxFuture;use futures::{ Future, FutureExt, StreamExt as _, channel::{ mpsc::{self, UnboundedSender}, oneshot, }, future::BoxFuture,};use gpui::{App, AsyncApp, BackgroundExecutor, Task};use http_client::HttpClient;use language::LanguageName;use lsp::LanguageServerName;use moka::sync::Cache;use node_runtime::NodeRuntime;use release_channel::ReleaseChannel;use semantic_version::SemanticVersion;use std::borrow::Cow;use std::sync::LazyLock;use std::{ path::{Path, PathBuf}, sync::Arc,};use wasmtime::{ CacheStore, Engine, Store, component::{Component, ResourceTable},};use wasmtime_wasi::{self as wasi, WasiView};use wit::Extension; pub struct WasmHost { engine: Engine, release_channel: ReleaseChannel, http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, pub(crate) proxy: Arc<ExtensionHostProxy>, fs: Arc<dyn Fs>, pub work_dir: PathBuf, _main_thread_message_task: Task<()>, main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,} #[derive(Clone)]pub struct WasmExtension { tx: UnboundedSender<ExtensionCall>, pub manifest: Arc<ExtensionManifest>, pub work_dir: Arc<Path>, #[allow(unused)] pub zed_api_version: SemanticVersion,} #[async_trait]impl extension::Extension for WasmExtension { fn manifest(&self) -> Arc<ExtensionManifest> { self.manifest.clone() } fn work_dir(&self) -> Arc<Path> { self.work_dir.clone() } async fn language_server_command( &self, language_server_id: LanguageServerName, language_name: LanguageName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Command> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let command = extension .call_language_server_command( store, &language_server_id, &language_name, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(command.into()) } .boxed() }) .await } async fn language_server_initialization_options( &self, language_server_id: LanguageServerName, language_name: LanguageName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_initialization_options( store, &language_server_id, &language_name, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_workspace_configuration( &self, language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_workspace_configuration( store, &language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_additional_initialization_options( &self, language_server_id: LanguageServerName, target_language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_additional_initialization_options( store, &language_server_id, &target_language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_additional_workspace_configuration( &self, language_server_id: LanguageServerName, target_language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_additional_workspace_configuration( store, &language_server_id, &target_language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn labels_for_completions( &self, language_server_id: LanguageServerName, completions: Vec<Completion>, ) -> Result<Vec<Option<CodeLabel>>> { self.call(|extension, store| { async move { let labels = extension .call_labels_for_completions( store, &language_server_id, completions.into_iter().map(Into::into).collect(), ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(labels .into_iter() .map(|label| label.map(Into::into)) .collect()) } .boxed() }) .await } async fn labels_for_symbols( &self, language_server_id: LanguageServerName, symbols: Vec<Symbol>, ) -> Result<Vec<Option<CodeLabel>>> { self.call(|extension, store| { async move { let labels = extension .call_labels_for_symbols( store, &language_server_id, symbols.into_iter().map(Into::into).collect(), ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(labels .into_iter() .map(|label| label.map(Into::into)) .collect()) } .boxed() }) .await } async fn complete_slash_command_argument( &self, command: SlashCommand, arguments: Vec<String>, ) -> Result<Vec<SlashCommandArgumentCompletion>> { self.call(|extension, store| { async move { let completions = extension .call_complete_slash_command_argument(store, &command.into(), &arguments) .await? .map_err(|err| anyhow!("{err}"))?; Ok(completions.into_iter().map(Into::into).collect()) } .boxed() }) .await } async fn run_slash_command( &self, command: SlashCommand, arguments: Vec<String>, delegate: Option<Arc<dyn WorktreeDelegate>>, ) -> Result<SlashCommandOutput> { self.call(|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, &command.into(), &arguments, resource) .await? .map_err(|err| anyhow!("{err}"))?; Ok(output.into()) } .boxed() }) .await } async fn context_server_command( &self, context_server_id: Arc<str>, project: Arc<dyn ProjectDelegate>, ) -> Result<Command> { self.call(|extension, store| { async move { let project_resource = store.data_mut().table().push(project)?; let command = extension .call_context_server_command(store, context_server_id.clone(), project_resource) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(command.into()) } .boxed() }) .await } async fn context_server_configuration( &self, context_server_id: Arc<str>, project: Arc<dyn ProjectDelegate>, ) -> Result<Option<ContextServerConfiguration>> { self.call(|extension, store| { async move { let project_resource = store.data_mut().table().push(project)?; let Some(configuration) = extension .call_context_server_configuration( store, context_server_id.clone(), project_resource, ) .await? .map_err(|err| anyhow!("{err}"))? else { return Ok(None); }; Ok(Some(configuration.try_into()?)) } .boxed() }) .await } async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> { self.call(|extension, store| { async move { let packages = extension .call_suggest_docs_packages(store, provider.as_ref()) .await? .map_err(|err| anyhow!("{err:?}"))?; Ok(packages) } .boxed() }) .await } async fn index_docs( &self, provider: Arc<str>, package_name: Arc<str>, kv_store: Arc<dyn KeyValueStoreDelegate>, ) -> Result<()> { self.call(|extension, store| { async move { let kv_store_resource = store.data_mut().table().push(kv_store)?; extension .call_index_docs( store, provider.as_ref(), package_name.as_ref(), kv_store_resource, ) .await? .map_err(|err| anyhow!("{err:?}"))?; anyhow::Ok(()) } .boxed() }) .await } async fn get_dap_binary( &self, dap_name: Arc<str>, config: DebugTaskDefinition, user_installed_path: Option<PathBuf>, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<DebugAdapterBinary> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let dap_binary = extension .call_get_dap_binary(store, dap_name, config, user_installed_path, resource) .await? .map_err(|err| anyhow!("{err:?}"))?; let dap_binary = dap_binary.try_into()?; Ok(dap_binary) } .boxed() }) .await } async fn get_dap_schema(&self) -> Result<serde_json::Value> { self.call(|extension, store| { async move { extension .call_dap_schema(store) .await .and_then(|schema| serde_json::to_value(schema).map_err(|err| err.to_string())) .map_err(|err| anyhow!(err.to_string())) } .boxed() }) .await }} pub struct WasmState { manifest: Arc<ExtensionManifest>, pub table: ResourceTable, ctx: wasi::WasiCtx, pub host: Arc<WasmHost>,} type MainThreadCall = Box<dyn Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, ()>>; type ExtensionCall = Box< dyn Send + for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>,>; fn wasm_engine() -> wasmtime::Engine { static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(|| { let mut config = wasmtime::Config::new(); config.wasm_component_model(true); config.async_support(true); config .enable_incremental_compilation(cache_store()) .unwrap(); wasmtime::Engine::new(&config).unwrap() }); WASM_ENGINE.clone()} fn cache_store() -> Arc<IncrementalCompilationCache> { static CACHE_STORE: LazyLock<Arc<IncrementalCompilationCache>> = LazyLock::new(|| Arc::new(IncrementalCompilationCache::new())); CACHE_STORE.clone()} impl WasmHost { pub fn new( fs: Arc<dyn Fs>, http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, proxy: Arc<ExtensionHostProxy>, work_dir: PathBuf, cx: &mut App, ) -> Arc<Self> { let (tx, mut rx) = mpsc::unbounded::<MainThreadCall>(); let task = cx.spawn(async move |cx| { while let Some(message) = rx.next().await { message(cx).await; } }); Arc::new(Self { engine: wasm_engine(), fs, work_dir, http_client, node_runtime, proxy, release_channel: ReleaseChannel::global(cx), _main_thread_message_task: task, main_thread_message_tx: tx, }) } pub fn load_extension( self: &Arc<Self>, wasm_bytes: Vec<u8>, 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 = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?; let component = Component::from_binary(&this.engine, &wasm_bytes) .context("failed to compile wasm component")?; let mut store = wasmtime::Store::new( &this.engine, WasmState { ctx: this.build_wasi_ctx(&manifest).await?, manifest: manifest.clone(), table: ResourceTable::new(), host: this.clone(), }, );Lines 1–500 of 726. Load more
pub mod wit; use crate::ExtensionManifest; use anyhow::{Context as _, Result, anyhow, bail}; use async_trait::async_trait; use extension::{ CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary, DebugTaskDefinition, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, }; use fs::{Fs, normalize_path}; use futures::future::LocalBoxFuture; use futures::{ Future, FutureExt, StreamExt as _, channel::{ mpsc::{self, UnboundedSender}, oneshot, }, future::BoxFuture, }; use gpui::{App, AsyncApp, BackgroundExecutor, Task}; use http_client::HttpClient; use language::LanguageName; use lsp::LanguageServerName; use moka::sync::Cache; use node_runtime::NodeRuntime; use release_channel::ReleaseChannel; use semantic_version::SemanticVersion; use std::borrow::Cow; use std::sync::LazyLock; use std::{ path::{Path, PathBuf}, sync::Arc, }; use wasmtime::{ CacheStore, Engine, Store, component::{Component, ResourceTable}, }; use wasmtime_wasi::{self as wasi, WasiView}; use wit::Extension; pub struct WasmHost { engine: Engine, release_channel: ReleaseChannel, http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, pub(crate) proxy: Arc<ExtensionHostProxy>, fs: Arc<dyn Fs>, pub work_dir: PathBuf, _main_thread_message_task: Task<()>, main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>, } #[derive(Clone)] pub struct WasmExtension { tx: UnboundedSender<ExtensionCall>, pub manifest: Arc<ExtensionManifest>, pub work_dir: Arc<Path>, #[allow(unused)] pub zed_api_version: SemanticVersion, } #[async_trait] impl extension::Extension for WasmExtension { fn manifest(&self) -> Arc<ExtensionManifest> { self.manifest.clone() } fn work_dir(&self) -> Arc<Path> { self.work_dir.clone() } async fn language_server_command( &self, language_server_id: LanguageServerName, language_name: LanguageName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Command> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let command = extension .call_language_server_command( store, &language_server_id, &language_name, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(command.into()) } .boxed() }) .await } async fn language_server_initialization_options( &self, language_server_id: LanguageServerName, language_name: LanguageName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_initialization_options( store, &language_server_id, &language_name, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_workspace_configuration( &self, language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_workspace_configuration( store, &language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_additional_initialization_options( &self, language_server_id: LanguageServerName, target_language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_additional_initialization_options( store, &language_server_id, &target_language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn language_server_additional_workspace_configuration( &self, language_server_id: LanguageServerName, target_language_server_id: LanguageServerName, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<Option<String>> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let options = extension .call_language_server_additional_workspace_configuration( store, &language_server_id, &target_language_server_id, resource, ) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(options) } .boxed() }) .await } async fn labels_for_completions( &self, language_server_id: LanguageServerName, completions: Vec<Completion>, ) -> Result<Vec<Option<CodeLabel>>> { self.call(|extension, store| { async move { let labels = extension .call_labels_for_completions( store, &language_server_id, completions.into_iter().map(Into::into).collect(), ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(labels .into_iter() .map(|label| label.map(Into::into)) .collect()) } .boxed() }) .await } async fn labels_for_symbols( &self, language_server_id: LanguageServerName, symbols: Vec<Symbol>, ) -> Result<Vec<Option<CodeLabel>>> { self.call(|extension, store| { async move { let labels = extension .call_labels_for_symbols( store, &language_server_id, symbols.into_iter().map(Into::into).collect(), ) .await? .map_err(|err| anyhow!("{err}"))?; Ok(labels .into_iter() .map(|label| label.map(Into::into)) .collect()) } .boxed() }) .await } async fn complete_slash_command_argument( &self, command: SlashCommand, arguments: Vec<String>, ) -> Result<Vec<SlashCommandArgumentCompletion>> { self.call(|extension, store| { async move { let completions = extension .call_complete_slash_command_argument(store, &command.into(), &arguments) .await? .map_err(|err| anyhow!("{err}"))?; Ok(completions.into_iter().map(Into::into).collect()) } .boxed() }) .await } async fn run_slash_command( &self, command: SlashCommand, arguments: Vec<String>, delegate: Option<Arc<dyn WorktreeDelegate>>, ) -> Result<SlashCommandOutput> { self.call(|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, &command.into(), &arguments, resource) .await? .map_err(|err| anyhow!("{err}"))?; Ok(output.into()) } .boxed() }) .await } async fn context_server_command( &self, context_server_id: Arc<str>, project: Arc<dyn ProjectDelegate>, ) -> Result<Command> { self.call(|extension, store| { async move { let project_resource = store.data_mut().table().push(project)?; let command = extension .call_context_server_command(store, context_server_id.clone(), project_resource) .await? .map_err(|err| anyhow!("{err}"))?; anyhow::Ok(command.into()) } .boxed() }) .await } async fn context_server_configuration( &self, context_server_id: Arc<str>, project: Arc<dyn ProjectDelegate>, ) -> Result<Option<ContextServerConfiguration>> { self.call(|extension, store| { async move { let project_resource = store.data_mut().table().push(project)?; let Some(configuration) = extension .call_context_server_configuration( store, context_server_id.clone(), project_resource, ) .await? .map_err(|err| anyhow!("{err}"))? else { return Ok(None); }; Ok(Some(configuration.try_into()?)) } .boxed() }) .await } async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> { self.call(|extension, store| { async move { let packages = extension .call_suggest_docs_packages(store, provider.as_ref()) .await? .map_err(|err| anyhow!("{err:?}"))?; Ok(packages) } .boxed() }) .await } async fn index_docs( &self, provider: Arc<str>, package_name: Arc<str>, kv_store: Arc<dyn KeyValueStoreDelegate>, ) -> Result<()> { self.call(|extension, store| { async move { let kv_store_resource = store.data_mut().table().push(kv_store)?; extension .call_index_docs( store, provider.as_ref(), package_name.as_ref(), kv_store_resource, ) .await? .map_err(|err| anyhow!("{err:?}"))?; anyhow::Ok(()) } .boxed() }) .await } async fn get_dap_binary( &self, dap_name: Arc<str>, config: DebugTaskDefinition, user_installed_path: Option<PathBuf>, worktree: Arc<dyn WorktreeDelegate>, ) -> Result<DebugAdapterBinary> { self.call(|extension, store| { async move { let resource = store.data_mut().table().push(worktree)?; let dap_binary = extension .call_get_dap_binary(store, dap_name, config, user_installed_path, resource) .await? .map_err(|err| anyhow!("{err:?}"))?; let dap_binary = dap_binary.try_into()?; Ok(dap_binary) } .boxed() }) .await } async fn get_dap_schema(&self) -> Result<serde_json::Value> { self.call(|extension, store| { async move { extension .call_dap_schema(store) .await .and_then(|schema| serde_json::to_value(schema).map_err(|err| err.to_string())) .map_err(|err| anyhow!(err.to_string())) } .boxed() }) .await } } pub struct WasmState { manifest: Arc<ExtensionManifest>, pub table: ResourceTable, ctx: wasi::WasiCtx, pub host: Arc<WasmHost>, } type MainThreadCall = Box<dyn Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, ()>>; type ExtensionCall = Box< dyn Send + for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>, >; fn wasm_engine() -> wasmtime::Engine { static WASM_ENGINE: LazyLock<wasmtime::Engine> = LazyLock::new(|| { let mut config = wasmtime::Config::new(); config.wasm_component_model(true); config.async_support(true); config .enable_incremental_compilation(cache_store()) .unwrap(); wasmtime::Engine::new(&config).unwrap() }); WASM_ENGINE.clone() } fn cache_store() -> Arc<IncrementalCompilationCache> { static CACHE_STORE: LazyLock<Arc<IncrementalCompilationCache>> = LazyLock::new(|| Arc::new(IncrementalCompilationCache::new())); CACHE_STORE.clone() } impl WasmHost { pub fn new( fs: Arc<dyn Fs>, http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, proxy: Arc<ExtensionHostProxy>, work_dir: PathBuf, cx: &mut App, ) -> Arc<Self> { let (tx, mut rx) = mpsc::unbounded::<MainThreadCall>(); let task = cx.spawn(async move |cx| { while let Some(message) = rx.next().await { message(cx).await; } }); Arc::new(Self { engine: wasm_engine(), fs, work_dir, http_client, node_runtime, proxy, release_channel: ReleaseChannel::global(cx), _main_thread_message_task: task, main_thread_message_tx: tx, }) } pub fn load_extension( self: &Arc<Self>, wasm_bytes: Vec<u8>, 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 = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?; let component = Component::from_binary(&this.engine, &wasm_bytes) .context("failed to compile wasm component")?; let mut store = wasmtime::Store::new( &this.engine, WasmState { ctx: this.build_wasi_ctx(&manifest).await?, manifest: manifest.clone(), table: ResourceTable::new(), host: this.clone(), }, );