From f92e6e9a959abfc6ec68c5261c8e9ed14686ea4f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 8 Nov 2024 16:39:21 -0500 Subject: [PATCH] Add support for context server extensions (#20250) This PR adds support for context servers provided by extensions. To provide a context server from an extension, you need to list the context servers in your `extension.toml`: ```toml [context_servers.my-context-server] ``` And then implement the `context_server_command` method to return the command that will be used to start the context server: ```rs use zed_extension_api::{self as zed, Command, ContextServerId, Result}; struct ExampleContextServerExtension; impl zed::Extension for ExampleContextServerExtension { fn new() -> Self { ExampleContextServerExtension } fn context_server_command(&mut self, _context_server_id: &ContextServerId) -> Result { Ok(Command { command: "node".to_string(), args: vec!["/path/to/example-context-server/index.js".to_string()], env: Vec::new(), }) } } zed::register_extension!(ExampleContextServerExtension); ``` Release Notes: - N/A --- Cargo.lock | 2 + crates/assistant/src/context_store.rs | 47 ++++++++++- crates/context_servers/src/context_servers.rs | 14 ++-- crates/context_servers/src/manager.rs | 34 ++++---- crates/context_servers/src/registry.rs | 72 +++++++++++++++++ crates/extension/src/extension_manifest.rs | 6 ++ crates/extension_api/src/extension_api.rs | 26 ++++++ .../wit/since_v0.2.0/extension.wit | 3 + crates/extension_host/src/extension_host.rs | 16 ++++ crates/extension_host/src/wasm_host/wit.rs | 18 +++++ crates/extensions_ui/Cargo.toml | 1 + .../src/extension_context_server.rs | 80 +++++++++++++++++++ .../src/extension_registration_hooks.rs | 30 +++++++ .../extensions_ui/src/extension_store_test.rs | 9 +++ crates/extensions_ui/src/extensions_ui.rs | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 2 + 17 files changed, 340 insertions(+), 22 deletions(-) create mode 100644 crates/context_servers/src/registry.rs create mode 100644 crates/extensions_ui/src/extension_context_server.rs diff --git a/Cargo.lock b/Cargo.lock index 957466997aee40eb077058f24ba6149b1f050d50..abc3eff861ef02b62a0cc1d0878fa4ced97a3d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4212,6 +4212,7 @@ dependencies = [ "async-trait", "client", "collections", + "context_servers", "ctor", "db", "editor", @@ -15353,6 +15354,7 @@ dependencies = [ "collections", "command_palette", "command_palette_hooks", + "context_servers", "copilot", "db", "diagnostics", diff --git a/crates/assistant/src/context_store.rs b/crates/assistant/src/context_store.rs index 868495d8907a250102b4c62154daa182be478961..80013a9c174ee48854232c2095626830d410d586 100644 --- a/crates/assistant/src/context_store.rs +++ b/crates/assistant/src/context_store.rs @@ -10,7 +10,7 @@ use clock::ReplicaId; use collections::HashMap; use command_palette_hooks::CommandPaletteFilter; use context_servers::manager::{ContextServerManager, ContextServerSettings}; -use context_servers::CONTEXT_SERVERS_NAMESPACE; +use context_servers::{ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE}; use fs::Fs; use futures::StreamExt; use fuzzy::StringMatchCandidate; @@ -51,8 +51,8 @@ pub struct ContextStore { contexts: Vec, contexts_metadata: Vec, context_server_manager: Model, - context_server_slash_command_ids: HashMap>, - context_server_tool_ids: HashMap>, + context_server_slash_command_ids: HashMap, Vec>, + context_server_tool_ids: HashMap, Vec>, host_contexts: Vec, fs: Arc, languages: Arc, @@ -148,6 +148,47 @@ impl ContextStore { this.handle_project_changed(project, cx); this.synchronize_contexts(cx); this.register_context_server_handlers(cx); + + // TODO: At the time when we construct the `ContextStore` we may not have yet initialized the extensions. + // In order to register the context servers when the extension is loaded, we're periodically looping to + // see if there are context servers to register. + // + // I tried doing this in a subscription on the `ExtensionStore`, but it never seemed to fire. + // + // We should find a more elegant way to do this. + let context_server_factory_registry = + ContextServerFactoryRegistry::default_global(cx); + cx.spawn(|context_store, mut cx| async move { + loop { + let mut servers_to_register = Vec::new(); + for (_id, factory) in + context_server_factory_registry.context_server_factories() + { + if let Some(server) = factory(&cx).await.log_err() { + servers_to_register.push(server); + } + } + + let Some(_) = context_store + .update(&mut cx, |this, cx| { + this.context_server_manager.update(cx, |this, cx| { + for server in servers_to_register { + this.add_server(server, cx).detach_and_log_err(cx); + } + }) + }) + .log_err() + else { + break; + }; + + smol::Timer::after(Duration::from_millis(100)).await; + } + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + this })?; this.update(&mut cx, |this, cx| this.reload(cx))? diff --git a/crates/context_servers/src/context_servers.rs b/crates/context_servers/src/context_servers.rs index c20b1ebdb188c84bfd72d707f97b92c9043cf486..af7d7f6218bc12258c07b02abf682e8ae128f6b6 100644 --- a/crates/context_servers/src/context_servers.rs +++ b/crates/context_servers/src/context_servers.rs @@ -1,13 +1,16 @@ +pub mod client; +pub mod manager; +pub mod protocol; +mod registry; +pub mod types; + use command_palette_hooks::CommandPaletteFilter; use gpui::{actions, AppContext}; use settings::Settings; +pub use crate::manager::ContextServer; use crate::manager::ContextServerSettings; - -pub mod client; -pub mod manager; -pub mod protocol; -pub mod types; +pub use crate::registry::ContextServerFactoryRegistry; actions!(context_servers, [Restart]); @@ -16,6 +19,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers"; pub fn init(cx: &mut AppContext) { ContextServerSettings::register(cx); + ContextServerFactoryRegistry::default_global(cx); CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE); diff --git a/crates/context_servers/src/manager.rs b/crates/context_servers/src/manager.rs index c94d01d7b7e15f914d8e610d0fb51337e391665e..5dd426bb7f22977ef97133a80fabb09a0e55b875 100644 --- a/crates/context_servers/src/manager.rs +++ b/crates/context_servers/src/manager.rs @@ -79,7 +79,7 @@ pub struct NativeContextServer { } impl NativeContextServer { - fn new(config: Arc) -> Self { + pub fn new(config: Arc) -> Self { Self { id: config.id.clone().into(), config, @@ -151,13 +151,13 @@ impl ContextServer for NativeContextServer { /// must go through the `GlobalContextServerManager` which holds /// a model to the ContextServerManager. pub struct ContextServerManager { - servers: HashMap>, - pending_servers: HashSet, + servers: HashMap, Arc>, + pending_servers: HashSet>, } pub enum Event { - ServerStarted { server_id: String }, - ServerStopped { server_id: String }, + ServerStarted { server_id: Arc }, + ServerStopped { server_id: Arc }, } impl EventEmitter for ContextServerManager {} @@ -178,10 +178,10 @@ impl ContextServerManager { pub fn add_server( &mut self, - config: Arc, + server: Arc, cx: &ModelContext, ) -> Task> { - let server_id = config.id.clone(); + let server_id = server.id(); if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) { return Task::ready(Ok(())); @@ -190,7 +190,6 @@ impl ContextServerManager { let task = { let server_id = server_id.clone(); cx.spawn(|this, mut cx| async move { - let server = Arc::new(NativeContextServer::new(config)); server.clone().start(&cx).await?; this.update(&mut cx, |this, cx| { this.servers.insert(server_id.clone(), server); @@ -211,14 +210,20 @@ impl ContextServerManager { self.servers.get(id).cloned() } - pub fn remove_server(&mut self, id: &str, cx: &ModelContext) -> Task> { - let id = id.to_string(); + pub fn remove_server( + &mut self, + id: &Arc, + cx: &ModelContext, + ) -> Task> { + let id = id.clone(); cx.spawn(|this, mut cx| async move { - if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? { + if let Some(server) = + this.update(&mut cx, |this, _cx| this.servers.remove(id.as_ref()))? + { server.stop()?; } this.update(&mut cx, |this, cx| { - this.pending_servers.remove(&id); + this.pending_servers.remove(id.as_ref()); cx.emit(Event::ServerStopped { server_id: id.clone(), }) @@ -232,7 +237,7 @@ impl ContextServerManager { id: &Arc, cx: &mut ModelContext, ) -> Task> { - let id = id.to_string(); + let id = id.clone(); cx.spawn(|this, mut cx| async move { if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? { server.stop()?; @@ -284,7 +289,8 @@ impl ContextServerManager { log::trace!("servers_to_add={:?}", servers_to_add); for config in servers_to_add { - self.add_server(Arc::new(config), cx).detach_and_log_err(cx); + let server = Arc::new(NativeContextServer::new(Arc::new(config))); + self.add_server(server, cx).detach_and_log_err(cx); } for id in servers_to_remove { diff --git a/crates/context_servers/src/registry.rs b/crates/context_servers/src/registry.rs new file mode 100644 index 0000000000000000000000000000000000000000..d34f0fbaa8a0357058fb4925e6bed7b60c4d4dd3 --- /dev/null +++ b/crates/context_servers/src/registry.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use anyhow::Result; +use collections::HashMap; +use gpui::{AppContext, AsyncAppContext, ReadGlobal}; +use gpui::{Global, Task}; +use parking_lot::RwLock; + +use crate::ContextServer; + +pub type ContextServerFactory = + Arc Task>> + Send + Sync + 'static>; + +#[derive(Default)] +struct GlobalContextServerFactoryRegistry(Arc); + +impl Global for GlobalContextServerFactoryRegistry {} + +#[derive(Default)] +struct ContextServerFactoryRegistryState { + context_servers: HashMap, ContextServerFactory>, +} + +#[derive(Default)] +pub struct ContextServerFactoryRegistry { + state: RwLock, +} + +impl ContextServerFactoryRegistry { + /// Returns the global [`ContextServerFactoryRegistry`]. + pub fn global(cx: &AppContext) -> Arc { + GlobalContextServerFactoryRegistry::global(cx).0.clone() + } + + /// Returns the global [`ContextServerFactoryRegistry`]. + /// + /// Inserts a default [`ContextServerFactoryRegistry`] if one does not yet exist. + pub fn default_global(cx: &mut AppContext) -> Arc { + cx.default_global::() + .0 + .clone() + } + + pub fn new() -> Arc { + Arc::new(Self { + state: RwLock::new(ContextServerFactoryRegistryState { + context_servers: HashMap::default(), + }), + }) + } + + pub fn context_server_factories(&self) -> Vec<(Arc, ContextServerFactory)> { + self.state + .read() + .context_servers + .iter() + .map(|(id, factory)| (id.clone(), factory.clone())) + .collect() + } + + /// Registers the provided [`ContextServerFactory`]. + pub fn register_server_factory(&self, id: Arc, factory: ContextServerFactory) { + let mut state = self.state.write(); + state.context_servers.insert(id, factory); + } + + /// Unregisters the [`ContextServerFactory`] for the server with the given ID. + pub fn unregister_server_factory_by_id(&self, server_id: &str) { + let mut state = self.state.write(); + state.context_servers.remove(server_id); + } +} diff --git a/crates/extension/src/extension_manifest.rs b/crates/extension/src/extension_manifest.rs index 3dfd7e0d41f34b8dbe7f70d42daacccce1b13f9d..77be30c6c007a387bae52e8b181809032cc59f0f 100644 --- a/crates/extension/src/extension_manifest.rs +++ b/crates/extension/src/extension_manifest.rs @@ -75,6 +75,8 @@ pub struct ExtensionManifest { #[serde(default)] pub language_servers: BTreeMap, #[serde(default)] + pub context_servers: BTreeMap, ContextServerManifestEntry>, + #[serde(default)] pub slash_commands: BTreeMap, SlashCommandManifestEntry>, #[serde(default)] pub indexed_docs_providers: BTreeMap, IndexedDocsProviderEntry>, @@ -134,6 +136,9 @@ impl LanguageServerManifestEntry { } } +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct ContextServerManifestEntry {} + #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct SlashCommandManifestEntry { pub description: String, @@ -205,6 +210,7 @@ fn manifest_from_old_manifest( .map(|grammar_name| (grammar_name, Default::default())) .collect(), language_servers: Default::default(), + context_servers: BTreeMap::default(), slash_commands: BTreeMap::default(), indexed_docs_providers: BTreeMap::default(), snippets: None, diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index f0b5baab9ab12819894de47b32dfb21d974056ab..b74ae6865357587c8d27615d087ff27188f48aef 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -129,6 +129,11 @@ pub trait Extension: Send + Sync { Err("`run_slash_command` not implemented".to_string()) } + /// Returns the command used to start a context server. + fn context_server_command(&mut self, _context_server_id: &ContextServerId) -> Result { + Err("`context_server_command` not implemented".to_string()) + } + /// Returns a list of package names as suggestions to be included in the /// search results of the `/docs` slash command. /// @@ -270,6 +275,11 @@ impl wit::Guest for Component { extension().run_slash_command(command, args, worktree) } + fn context_server_command(context_server_id: String) -> Result { + let context_server_id = ContextServerId(context_server_id); + extension().context_server_command(&context_server_id) + } + fn suggest_docs_packages(provider: String) -> Result, String> { extension().suggest_docs_packages(provider) } @@ -299,6 +309,22 @@ impl fmt::Display for LanguageServerId { } } +/// The ID of a context server. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct ContextServerId(String); + +impl AsRef for ContextServerId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for ContextServerId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + impl CodeLabelSpan { /// Returns a [`CodeLabelSpan::CodeRange`]. pub fn code_range(range: impl Into) -> Self { diff --git a/crates/extension_api/wit/since_v0.2.0/extension.wit b/crates/extension_api/wit/since_v0.2.0/extension.wit index c7599f93ffd55d0d5bf848876fd8a0b725f324bd..1ffbc90778f2d5673ba9ca4d4243ea38a3312e2c 100644 --- a/crates/extension_api/wit/since_v0.2.0/extension.wit +++ b/crates/extension_api/wit/since_v0.2.0/extension.wit @@ -135,6 +135,9 @@ world extension { /// Returns the output from running the provided slash command. export run-slash-command: func(command: slash-command, args: list, worktree: option>) -> result; + /// Returns the command used to start up a context server. + export context-server-command: func(context-server-id: string) -> result; + /// Returns a list of packages as suggestions to be included in the `/docs` /// search results. /// diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index c69463c0f12d14dc69d1a0276a9dbdd5ec360a45..c10a435e2daebcd49e3f878d7dd9e241f933b19f 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -145,6 +145,14 @@ pub trait ExtensionRegistrationHooks: Send + Sync + 'static { ) { } + fn register_context_server( + &self, + _id: Arc, + _extension: WasmExtension, + _host: Arc, + ) { + } + fn register_docs_provider( &self, _extension: WasmExtension, @@ -1267,6 +1275,14 @@ impl ExtensionStore { ); } + for (id, _context_server_entry) in &manifest.context_servers { + this.registration_hooks.register_context_server( + id.clone(), + wasm_extension.clone(), + this.wasm_host.clone(), + ); + } + for (provider_id, _provider) in &manifest.indexed_docs_providers { this.registration_hooks.register_docs_provider( wasm_extension.clone(), diff --git a/crates/extension_host/src/wasm_host/wit.rs b/crates/extension_host/src/wasm_host/wit.rs index 593d46297256ef653a57e13ee3376ca880b64ebf..ae85c35e9416573e9b9f084cb5419bfd1c8c19e9 100644 --- a/crates/extension_host/src/wasm_host/wit.rs +++ b/crates/extension_host/src/wasm_host/wit.rs @@ -384,6 +384,24 @@ impl Extension { } } + pub async fn call_context_server_command( + &self, + store: &mut Store, + context_server_id: Arc, + ) -> Result> { + match self { + Extension::V020(ext) => { + ext.call_context_server_command(store, &context_server_id) + .await + } + Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => { + Err(anyhow!( + "`context_server_command` not available prior to v0.2.0" + )) + } + } + } + pub async fn call_suggest_docs_packages( &self, store: &mut Store, diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index f68c5a54706d0dff7eb6c043b056c12d6b6a8abb..96c62d3bfb9186e6379b54dfb2a9f574f1c5363b 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -20,6 +20,7 @@ assistant_slash_command.workspace = true async-trait.workspace = true client.workspace = true collections.workspace = true +context_servers.workspace = true db.workspace = true editor.workspace = true extension_host.workspace = true diff --git a/crates/extensions_ui/src/extension_context_server.rs b/crates/extensions_ui/src/extension_context_server.rs new file mode 100644 index 0000000000000000000000000000000000000000..7c48ff89a33402c6f30c1b77abc35aeff551ec41 --- /dev/null +++ b/crates/extensions_ui/src/extension_context_server.rs @@ -0,0 +1,80 @@ +use std::pin::Pin; +use std::sync::Arc; + +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use context_servers::manager::{NativeContextServer, ServerConfig}; +use context_servers::protocol::InitializedContextServerProtocol; +use context_servers::ContextServer; +use extension_host::wasm_host::{WasmExtension, WasmHost}; +use futures::{Future, FutureExt}; +use gpui::AsyncAppContext; + +pub struct ExtensionContextServer { + #[allow(unused)] + pub(crate) extension: WasmExtension, + #[allow(unused)] + pub(crate) host: Arc, + id: Arc, + context_server: Arc, +} + +impl ExtensionContextServer { + pub async fn new(extension: WasmExtension, host: Arc, id: Arc) -> Result { + let command = extension + .call({ + let id = id.clone(); + |extension, store| { + async move { + let command = extension + .call_context_server_command(store, id.clone()) + .await? + .map_err(|e| anyhow!("{}", e))?; + anyhow::Ok(command) + } + .boxed() + } + }) + .await?; + + let config = Arc::new(ServerConfig { + id: id.to_string(), + executable: command.command, + args: command.args, + env: Some(command.env.into_iter().collect()), + }); + + anyhow::Ok(Self { + extension, + host, + id, + context_server: Arc::new(NativeContextServer::new(config)), + }) + } +} + +#[async_trait(?Send)] +impl ContextServer for ExtensionContextServer { + fn id(&self) -> Arc { + self.id.clone() + } + + fn config(&self) -> Arc { + self.context_server.config() + } + + fn client(&self) -> Option> { + self.context_server.client() + } + + fn start<'a>( + self: Arc, + cx: &'a AsyncAppContext, + ) -> Pin>>> { + self.context_server.clone().start(cx) + } + + fn stop(&self) -> Result<()> { + self.context_server.stop() + } +} diff --git a/crates/extensions_ui/src/extension_registration_hooks.rs b/crates/extensions_ui/src/extension_registration_hooks.rs index 0dcedf37aad4a9acbfbb074ae12f4902b4e213e1..37fe658894b264f8d45f5d59809bee5fa14d29f1 100644 --- a/crates/extensions_ui/src/extension_registration_hooks.rs +++ b/crates/extensions_ui/src/extension_registration_hooks.rs @@ -2,6 +2,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::Result; use assistant_slash_command::SlashCommandRegistry; +use context_servers::ContextServerFactoryRegistry; use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host}; use fs::Fs; use gpui::{AppContext, BackgroundExecutor, Task}; @@ -11,6 +12,7 @@ use snippet_provider::SnippetRegistry; use theme::{ThemeRegistry, ThemeSettings}; use ui::SharedString; +use crate::extension_context_server::ExtensionContextServer; use crate::{extension_indexed_docs_provider, extension_slash_command::ExtensionSlashCommand}; pub struct ConcreteExtensionRegistrationHooks { @@ -19,6 +21,7 @@ pub struct ConcreteExtensionRegistrationHooks { indexed_docs_registry: Arc, snippet_registry: Arc, language_registry: Arc, + context_server_factory_registry: Arc, executor: BackgroundExecutor, } @@ -29,6 +32,7 @@ impl ConcreteExtensionRegistrationHooks { indexed_docs_registry: Arc, snippet_registry: Arc, language_registry: Arc, + context_server_factory_registry: Arc, cx: &AppContext, ) -> Arc { Arc::new(Self { @@ -37,6 +41,7 @@ impl ConcreteExtensionRegistrationHooks { indexed_docs_registry, snippet_registry, language_registry, + context_server_factory_registry, executor: cx.background_executor().clone(), }) } @@ -69,6 +74,31 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio ) } + fn register_context_server( + &self, + id: Arc, + extension: wasm_host::WasmExtension, + host: Arc, + ) { + self.context_server_factory_registry + .register_server_factory( + id.clone(), + Arc::new({ + move |cx| { + let id = id.clone(); + let extension = extension.clone(); + let host = host.clone(); + cx.spawn(|_cx| async move { + let context_server = + ExtensionContextServer::new(extension, host, id).await?; + + anyhow::Ok(Arc::new(context_server) as _) + }) + } + }), + ); + } + fn register_docs_provider( &self, extension: wasm_host::WasmExtension, diff --git a/crates/extensions_ui/src/extension_store_test.rs b/crates/extensions_ui/src/extension_store_test.rs index 364f2397ddb8f67ac4c61d22812dd8fdc8e980de..88730e8eee65f13d21e7ff5209927cb400fd46a3 100644 --- a/crates/extensions_ui/src/extension_store_test.rs +++ b/crates/extensions_ui/src/extension_store_test.rs @@ -1,6 +1,7 @@ use assistant_slash_command::SlashCommandRegistry; use async_compression::futures::bufread::GzipEncoder; use collections::BTreeMap; +use context_servers::ContextServerFactoryRegistry; use extension_host::ExtensionSettings; use extension_host::SchemaVersion; use extension_host::{ @@ -161,6 +162,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { .into_iter() .collect(), language_servers: BTreeMap::default(), + context_servers: BTreeMap::default(), slash_commands: BTreeMap::default(), indexed_docs_providers: BTreeMap::default(), snippets: None, @@ -187,6 +189,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { languages: Default::default(), grammars: BTreeMap::default(), language_servers: BTreeMap::default(), + context_servers: BTreeMap::default(), slash_commands: BTreeMap::default(), indexed_docs_providers: BTreeMap::default(), snippets: None, @@ -264,6 +267,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { let slash_command_registry = SlashCommandRegistry::new(); let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor())); let snippet_registry = Arc::new(SnippetRegistry::new()); + let context_server_factory_registry = ContextServerFactoryRegistry::new(); let node_runtime = NodeRuntime::unavailable(); let store = cx.new_model(|cx| { @@ -273,6 +277,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { indexed_docs_registry.clone(), snippet_registry.clone(), language_registry.clone(), + context_server_factory_registry.clone(), cx, ); @@ -356,6 +361,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { languages: Default::default(), grammars: BTreeMap::default(), language_servers: BTreeMap::default(), + context_servers: BTreeMap::default(), slash_commands: BTreeMap::default(), indexed_docs_providers: BTreeMap::default(), snippets: None, @@ -406,6 +412,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { indexed_docs_registry, snippet_registry, language_registry.clone(), + context_server_factory_registry.clone(), cx, ); @@ -500,6 +507,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { let slash_command_registry = SlashCommandRegistry::new(); let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor())); let snippet_registry = Arc::new(SnippetRegistry::new()); + let context_server_factory_registry = ContextServerFactoryRegistry::new(); let node_runtime = NodeRuntime::unavailable(); let mut status_updates = language_registry.language_server_binary_statuses(); @@ -596,6 +604,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { indexed_docs_registry, snippet_registry, language_registry.clone(), + context_server_factory_registry.clone(), cx, ); ExtensionStore::new( diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 8952effaadf65fd0060b060bf3abd7f554a1a676..c46605a29aafac855cf767d7fbe5e906f91bcecb 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1,4 +1,5 @@ mod components; +mod extension_context_server; mod extension_indexed_docs_provider; mod extension_registration_hooks; mod extension_slash_command; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index bebd045ba2b6457b82a9eec51c742f571ab5966d..5fb94f2573c871680ca5a774ebd8d837b702b2fb 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -35,6 +35,7 @@ collab_ui.workspace = true collections.workspace = true command_palette.workspace = true command_palette_hooks.workspace = true +context_servers.workspace = true copilot.workspace = true db.workspace = true diagnostics.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e5f7817aec63216fbfe9b15177429c3437d15165..0ba960c0aa717b5025f3b115c1f1618e74e52ecf 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -13,6 +13,7 @@ use clap::{command, Parser}; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::{parse_zed_link, Client, ProxySettings, UserStore}; use collab_ui::channel_view::ChannelView; +use context_servers::ContextServerFactoryRegistry; use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE}; use editor::Editor; use env_logger::Builder; @@ -411,6 +412,7 @@ fn main() { IndexedDocsRegistry::global(cx), SnippetRegistry::global(cx), app_state.languages.clone(), + ContextServerFactoryRegistry::global(cx), cx, ); extension_host::init(