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<Command> {
        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
This commit is contained in:
Marshall Bowers 2024-11-08 16:39:21 -05:00 committed by GitHub
parent ff4f67993b
commit f92e6e9a95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 340 additions and 22 deletions

View file

@ -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<WasmHost>,
id: Arc<str>,
context_server: Arc<NativeContextServer>,
}
impl ExtensionContextServer {
pub async fn new(extension: WasmExtension, host: Arc<WasmHost>, id: Arc<str>) -> Result<Self> {
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<str> {
self.id.clone()
}
fn config(&self) -> Arc<ServerConfig> {
self.context_server.config()
}
fn client(&self) -> Option<Arc<InitializedContextServerProtocol>> {
self.context_server.client()
}
fn start<'a>(
self: Arc<Self>,
cx: &'a AsyncAppContext,
) -> Pin<Box<dyn 'a + Future<Output = Result<()>>>> {
self.context_server.clone().start(cx)
}
fn stop(&self) -> Result<()> {
self.context_server.stop()
}
}