Expose context server settings to extensions (#20555)
This PR exposes context server settings to extensions. Extensions can use `ContextServerSettings::for_project` to get the context server settings for the current project. The `experimental.context_servers` setting has been removed and replaced with the `context_servers` setting (which is now an object instead of an array). Release Notes: - N/A --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
0a9c78a58d
commit
3ebb64ea1d
17 changed files with 243 additions and 126 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2823,6 +2823,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"postage",
|
"postage",
|
||||||
|
"project",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -4170,6 +4171,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"client",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
|
"context_servers",
|
||||||
"ctor",
|
"ctor",
|
||||||
"env_logger 0.11.5",
|
"env_logger 0.11.5",
|
||||||
"extension",
|
"extension",
|
||||||
|
|
|
@ -1182,15 +1182,6 @@
|
||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
"ssh_connections": [],
|
"ssh_connections": [],
|
||||||
// Configures the Context Server Protocol binaries
|
// Configures context servers for use in the Assistant.
|
||||||
//
|
"context_servers": {}
|
||||||
// Examples:
|
|
||||||
// {
|
|
||||||
// "id": "server-1",
|
|
||||||
// "executable": "/path",
|
|
||||||
// "args": ['arg1", "args2"]
|
|
||||||
// }
|
|
||||||
"experimental.context_servers": {
|
|
||||||
"servers": []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,49 +145,52 @@ impl ContextStore {
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
};
|
};
|
||||||
this.handle_project_changed(project, cx);
|
this.handle_project_changed(project.clone(), cx);
|
||||||
this.synchronize_contexts(cx);
|
this.synchronize_contexts(cx);
|
||||||
this.register_context_server_handlers(cx);
|
this.register_context_server_handlers(cx);
|
||||||
|
|
||||||
// TODO: At the time when we construct the `ContextStore` we may not have yet initialized the extensions.
|
if project.read(cx).is_local() {
|
||||||
// In order to register the context servers when the extension is loaded, we're periodically looping to
|
// TODO: At the time when we construct the `ContextStore` we may not have yet initialized the extensions.
|
||||||
// see if there are context servers to register.
|
// 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.
|
//
|
||||||
//
|
// 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 =
|
// We should find a more elegant way to do this.
|
||||||
ContextServerFactoryRegistry::default_global(cx);
|
let context_server_factory_registry =
|
||||||
cx.spawn(|context_store, mut cx| async move {
|
ContextServerFactoryRegistry::default_global(cx);
|
||||||
loop {
|
cx.spawn(|context_store, mut cx| async move {
|
||||||
let mut servers_to_register = Vec::new();
|
loop {
|
||||||
for (_id, factory) in
|
let mut servers_to_register = Vec::new();
|
||||||
context_server_factory_registry.context_server_factories()
|
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);
|
if let Some(server) = factory(project.clone(), &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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(_) = context_store
|
anyhow::Ok(())
|
||||||
.update(&mut cx, |this, cx| {
|
})
|
||||||
this.context_server_manager.update(cx, |this, cx| {
|
.detach_and_log_err(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
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -21,6 +21,7 @@ gpui.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
|
project.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
|
@ -53,7 +53,7 @@ pub struct Client {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct ContextServerId(pub String);
|
pub struct ContextServerId(pub Arc<str>);
|
||||||
|
|
||||||
fn is_null_value<T: Serialize>(value: &T) -> bool {
|
fn is_null_value<T: Serialize>(value: &T) -> bool {
|
||||||
if let Ok(Value::Null) = serde_json::to_value(value) {
|
if let Ok(Value::Null) = serde_json::to_value(value) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ use std::path::Path;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
|
@ -36,19 +36,25 @@ use crate::{
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||||
pub struct ContextServerSettings {
|
pub struct ContextServerSettings {
|
||||||
pub servers: Vec<ServerConfig>,
|
#[serde(default)]
|
||||||
|
pub context_servers: HashMap<Arc<str>, ServerConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub command: Option<ServerCommand>,
|
||||||
|
pub settings: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerCommand {
|
||||||
pub id: String,
|
pub path: String,
|
||||||
pub executable: String,
|
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
pub env: Option<HashMap<String, String>>,
|
pub env: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings for ContextServerSettings {
|
impl Settings for ContextServerSettings {
|
||||||
const KEY: Option<&'static str> = Some("experimental.context_servers");
|
const KEY: Option<&'static str> = None;
|
||||||
|
|
||||||
type FileContent = Self;
|
type FileContent = Self;
|
||||||
|
|
||||||
|
@ -79,9 +85,9 @@ pub struct NativeContextServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeContextServer {
|
impl NativeContextServer {
|
||||||
pub fn new(config: Arc<ServerConfig>) -> Self {
|
pub fn new(id: Arc<str>, config: Arc<ServerConfig>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: config.id.clone().into(),
|
id,
|
||||||
config,
|
config,
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
}
|
}
|
||||||
|
@ -107,13 +113,16 @@ impl ContextServer for NativeContextServer {
|
||||||
cx: &'a AsyncAppContext,
|
cx: &'a AsyncAppContext,
|
||||||
) -> Pin<Box<dyn 'a + Future<Output = Result<()>>>> {
|
) -> Pin<Box<dyn 'a + Future<Output = Result<()>>>> {
|
||||||
async move {
|
async move {
|
||||||
log::info!("starting context server {}", self.config.id,);
|
log::info!("starting context server {}", self.id);
|
||||||
|
let Some(command) = &self.config.command else {
|
||||||
|
bail!("no command specified for server {}", self.id);
|
||||||
|
};
|
||||||
let client = Client::new(
|
let client = Client::new(
|
||||||
client::ContextServerId(self.config.id.clone()),
|
client::ContextServerId(self.id.clone()),
|
||||||
client::ModelContextServerBinary {
|
client::ModelContextServerBinary {
|
||||||
executable: Path::new(&self.config.executable).to_path_buf(),
|
executable: Path::new(&command.path).to_path_buf(),
|
||||||
args: self.config.args.clone(),
|
args: command.args.clone(),
|
||||||
env: self.config.env.clone(),
|
env: command.env.clone(),
|
||||||
},
|
},
|
||||||
cx.clone(),
|
cx.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
@ -127,7 +136,7 @@ impl ContextServer for NativeContextServer {
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"context server {} initialized: {:?}",
|
"context server {} initialized: {:?}",
|
||||||
self.config.id,
|
self.id,
|
||||||
initialized_protocol.initialize,
|
initialized_protocol.initialize,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -242,7 +251,7 @@ impl ContextServerManager {
|
||||||
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))? {
|
||||||
server.stop()?;
|
server.stop()?;
|
||||||
let config = server.config();
|
let config = server.config();
|
||||||
let new_server = Arc::new(NativeContextServer::new(config));
|
let new_server = Arc::new(NativeContextServer::new(id.clone(), config));
|
||||||
new_server.clone().start(&cx).await?;
|
new_server.clone().start(&cx).await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.servers.insert(id.clone(), new_server);
|
this.servers.insert(id.clone(), new_server);
|
||||||
|
@ -270,15 +279,15 @@ impl ContextServerManager {
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let new_servers = settings
|
let new_servers = settings
|
||||||
.servers
|
.context_servers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|config| (config.id.clone(), config.clone()))
|
.map(|(id, config)| (id.clone(), config.clone()))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let servers_to_add = new_servers
|
let servers_to_add = new_servers
|
||||||
.values()
|
.iter()
|
||||||
.filter(|config| !current_servers.contains_key(config.id.as_str()))
|
.filter(|(id, _)| !current_servers.contains_key(id.as_ref()))
|
||||||
.cloned()
|
.map(|(id, config)| (id.clone(), config.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let servers_to_remove = current_servers
|
let servers_to_remove = current_servers
|
||||||
|
@ -288,9 +297,11 @@ impl ContextServerManager {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
log::trace!("servers_to_add={:?}", servers_to_add);
|
log::trace!("servers_to_add={:?}", servers_to_add);
|
||||||
for config in servers_to_add {
|
for (id, config) in servers_to_add {
|
||||||
let server = Arc::new(NativeContextServer::new(Arc::new(config)));
|
if config.command.is_some() {
|
||||||
self.add_server(server, cx).detach_and_log_err(cx);
|
let server = Arc::new(NativeContextServer::new(id, Arc::new(config)));
|
||||||
|
self.add_server(server, cx).detach_and_log_err(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for id in servers_to_remove {
|
for id in servers_to_remove {
|
||||||
|
|
|
@ -2,14 +2,18 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, AsyncAppContext, ReadGlobal};
|
use gpui::{AppContext, AsyncAppContext, Global, Model, ReadGlobal, Task};
|
||||||
use gpui::{Global, Task};
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use project::Project;
|
||||||
|
|
||||||
use crate::ContextServer;
|
use crate::ContextServer;
|
||||||
|
|
||||||
pub type ContextServerFactory =
|
pub type ContextServerFactory = Arc<
|
||||||
Arc<dyn Fn(&AsyncAppContext) -> Task<Result<Arc<dyn ContextServer>>> + Send + Sync + 'static>;
|
dyn Fn(Model<Project>, &AsyncAppContext) -> Task<Result<Arc<dyn ContextServer>>>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct GlobalContextServerFactoryRegistry(Arc<ContextServerFactoryRegistry>);
|
struct GlobalContextServerFactoryRegistry(Arc<ContextServerFactoryRegistry>);
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub use wit::{
|
||||||
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
|
||||||
},
|
},
|
||||||
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
|
||||||
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
|
KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Undocumented WIT re-exports.
|
// Undocumented WIT re-exports.
|
||||||
|
@ -130,7 +130,11 @@ pub trait Extension: Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the command used to start a context server.
|
/// Returns the command used to start a context server.
|
||||||
fn context_server_command(&mut self, _context_server_id: &ContextServerId) -> Result<Command> {
|
fn context_server_command(
|
||||||
|
&mut self,
|
||||||
|
_context_server_id: &ContextServerId,
|
||||||
|
_project: &Project,
|
||||||
|
) -> Result<Command> {
|
||||||
Err("`context_server_command` not implemented".to_string())
|
Err("`context_server_command` not implemented".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,9 +279,12 @@ impl wit::Guest for Component {
|
||||||
extension().run_slash_command(command, args, worktree)
|
extension().run_slash_command(command, args, worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn context_server_command(context_server_id: String) -> Result<wit::Command> {
|
fn context_server_command(
|
||||||
|
context_server_id: String,
|
||||||
|
project: &Project,
|
||||||
|
) -> Result<wit::Command> {
|
||||||
let context_server_id = ContextServerId(context_server_id);
|
let context_server_id = ContextServerId(context_server_id);
|
||||||
extension().context_server_command(&context_server_id)
|
extension().context_server_command(&context_server_id, project)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
|
||||||
|
|
|
@ -1,34 +1,56 @@
|
||||||
//! Provides access to Zed settings.
|
//! Provides access to Zed settings.
|
||||||
|
|
||||||
#[path = "../wit/since_v0.1.0/settings.rs"]
|
#[path = "../wit/since_v0.2.0/settings.rs"]
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
use crate::{wit, Result, SettingsLocation, Worktree};
|
use crate::{wit, Project, Result, SettingsLocation, Worktree};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
impl LanguageSettings {
|
impl LanguageSettings {
|
||||||
/// Returns the [`LanguageSettings`] for the given language.
|
/// Returns the [`LanguageSettings`] for the given language.
|
||||||
pub fn for_worktree(language: Option<&str>, worktree: &Worktree) -> Result<Self> {
|
pub fn for_worktree(language: Option<&str>, worktree: &Worktree) -> Result<Self> {
|
||||||
let location = SettingsLocation {
|
get_settings("language", language, Some(worktree.id()))
|
||||||
worktree_id: worktree.id(),
|
|
||||||
path: worktree.root_path(),
|
|
||||||
};
|
|
||||||
let settings_json = wit::get_settings(Some(&location), "language", language)?;
|
|
||||||
let settings: Self = serde_json::from_str(&settings_json).map_err(|err| err.to_string())?;
|
|
||||||
Ok(settings)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LspSettings {
|
impl LspSettings {
|
||||||
/// Returns the [`LspSettings`] for the given language server.
|
/// Returns the [`LspSettings`] for the given language server.
|
||||||
pub fn for_worktree(language_server_name: &str, worktree: &Worktree) -> Result<Self> {
|
pub fn for_worktree(language_server_name: &str, worktree: &Worktree) -> Result<Self> {
|
||||||
let location = SettingsLocation {
|
get_settings("lsp", Some(language_server_name), Some(worktree.id()))
|
||||||
worktree_id: worktree.id(),
|
|
||||||
path: worktree.root_path(),
|
|
||||||
};
|
|
||||||
let settings_json = wit::get_settings(Some(&location), "lsp", Some(language_server_name))?;
|
|
||||||
let settings: Self = serde_json::from_str(&settings_json).map_err(|err| err.to_string())?;
|
|
||||||
Ok(settings)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContextServerSettings {
|
||||||
|
/// Returns the [`ContextServerSettings`] for the given context server.
|
||||||
|
pub fn for_project(context_server_id: &str, project: &Project) -> Result<Self> {
|
||||||
|
let global_setting: Self = get_settings("context_servers", Some(context_server_id), None)?;
|
||||||
|
|
||||||
|
for worktree_id in project.worktree_ids() {
|
||||||
|
let settings = get_settings(
|
||||||
|
"context_servers",
|
||||||
|
Some(context_server_id),
|
||||||
|
Some(worktree_id),
|
||||||
|
)?;
|
||||||
|
if settings != global_setting {
|
||||||
|
return Ok(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(global_setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_settings<T: serde::de::DeserializeOwned>(
|
||||||
|
settings_type: &str,
|
||||||
|
settings_name: Option<&str>,
|
||||||
|
worktree_id: Option<u64>,
|
||||||
|
) -> Result<T> {
|
||||||
|
let location = worktree_id.map(|worktree_id| SettingsLocation {
|
||||||
|
worktree_id,
|
||||||
|
path: String::new(),
|
||||||
|
});
|
||||||
|
let settings_json = wit::get_settings(location.as_ref(), settings_type, settings_name)?;
|
||||||
|
let settings: T = serde_json::from_str(&settings_json).map_err(|err| err.to_string())?;
|
||||||
|
Ok(settings)
|
||||||
|
}
|
||||||
|
|
|
@ -83,6 +83,12 @@ world extension {
|
||||||
shell-env: func() -> env-vars;
|
shell-env: func() -> env-vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Zed project.
|
||||||
|
resource project {
|
||||||
|
/// Returns the IDs of all of the worktrees in this project.
|
||||||
|
worktree-ids: func() -> list<u64>;
|
||||||
|
}
|
||||||
|
|
||||||
/// A key-value store.
|
/// A key-value store.
|
||||||
resource key-value-store {
|
resource key-value-store {
|
||||||
/// Inserts an entry under the specified key.
|
/// Inserts an entry under the specified key.
|
||||||
|
@ -136,7 +142,7 @@ world extension {
|
||||||
export run-slash-command: func(command: slash-command, args: list<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
|
export run-slash-command: func(command: slash-command, args: list<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
|
||||||
|
|
||||||
/// Returns the command used to start up a context server.
|
/// Returns the command used to start up a context server.
|
||||||
export context-server-command: func(context-server-id: string) -> result<command, string>;
|
export context-server-command: func(context-server-id: string, project: borrow<project>) -> result<command, string>;
|
||||||
|
|
||||||
/// Returns a list of packages as suggestions to be included in the `/docs`
|
/// Returns a list of packages as suggestions to be included in the `/docs`
|
||||||
/// search results.
|
/// search results.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::num::NonZeroU32;
|
use std::{collections::HashMap, num::NonZeroU32};
|
||||||
|
|
||||||
/// The settings for a particular language.
|
/// The settings for a particular language.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -12,18 +12,29 @@ pub struct LanguageSettings {
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct LspSettings {
|
pub struct LspSettings {
|
||||||
/// The settings for the language server binary.
|
/// The settings for the language server binary.
|
||||||
pub binary: Option<BinarySettings>,
|
pub binary: Option<CommandSettings>,
|
||||||
/// The initialization options to pass to the language server.
|
/// The initialization options to pass to the language server.
|
||||||
pub initialization_options: Option<serde_json::Value>,
|
pub initialization_options: Option<serde_json::Value>,
|
||||||
/// The settings to pass to language server.
|
/// The settings to pass to language server.
|
||||||
pub settings: Option<serde_json::Value>,
|
pub settings: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The settings for a language server binary.
|
/// The settings for a particular context server.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct BinarySettings {
|
pub struct ContextServerSettings {
|
||||||
/// The path to the binary.
|
/// The settings for the context server binary.
|
||||||
pub path: Option<String>,
|
pub command: Option<CommandSettings>,
|
||||||
/// The arguments to pass to the binary.
|
/// The settings to pass to the context server.
|
||||||
pub arguments: Option<Vec<String>>,
|
pub settings: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The settings for a command.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct CommandSettings {
|
||||||
|
/// The path to the command.
|
||||||
|
pub path: Option<String>,
|
||||||
|
/// The arguments to pass to the command.
|
||||||
|
pub arguments: Option<Vec<String>>,
|
||||||
|
/// The environment variables.
|
||||||
|
pub env: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ async-tar.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
context_servers.workspace = true
|
||||||
extension.workspace = true
|
extension.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
|
|
@ -27,7 +27,7 @@ use wasmtime::{
|
||||||
};
|
};
|
||||||
use wasmtime_wasi as wasi;
|
use wasmtime_wasi as wasi;
|
||||||
use wit::Extension;
|
use wit::Extension;
|
||||||
pub use wit::SlashCommand;
|
pub use wit::{ExtensionProject, SlashCommand};
|
||||||
|
|
||||||
pub struct WasmHost {
|
pub struct WasmHost {
|
||||||
engine: Engine,
|
engine: Engine,
|
||||||
|
|
|
@ -4,7 +4,6 @@ mod since_v0_0_6;
|
||||||
mod since_v0_1_0;
|
mod since_v0_1_0;
|
||||||
mod since_v0_2_0;
|
mod since_v0_2_0;
|
||||||
use lsp::LanguageServerName;
|
use lsp::LanguageServerName;
|
||||||
// use indexed_docs::IndexedDocsDatabase;
|
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
use since_v0_2_0 as latest;
|
use since_v0_2_0 as latest;
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ pub use latest::{
|
||||||
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
|
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
|
||||||
},
|
},
|
||||||
zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
|
zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
|
||||||
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
|
CodeLabel, CodeLabelSpan, Command, ExtensionProject, Range, SlashCommand,
|
||||||
};
|
};
|
||||||
pub use since_v0_0_4::LanguageServerConfig;
|
pub use since_v0_0_4::LanguageServerConfig;
|
||||||
|
|
||||||
|
@ -389,10 +388,11 @@ impl Extension {
|
||||||
&self,
|
&self,
|
||||||
store: &mut Store<WasmState>,
|
store: &mut Store<WasmState>,
|
||||||
context_server_id: Arc<str>,
|
context_server_id: Arc<str>,
|
||||||
|
project: Resource<ExtensionProject>,
|
||||||
) -> Result<Result<Command, String>> {
|
) -> Result<Result<Command, String>> {
|
||||||
match self {
|
match self {
|
||||||
Extension::V020(ext) => {
|
Extension::V020(ext) => {
|
||||||
ext.call_context_server_command(store, &context_server_id)
|
ext.call_context_server_command(store, &context_server_id, project)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
|
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use context_servers::manager::ContextServerSettings;
|
||||||
use futures::{io::BufReader, FutureExt as _};
|
use futures::{io::BufReader, FutureExt as _};
|
||||||
use futures::{lock::Mutex, AsyncReadExt};
|
use futures::{lock::Mutex, AsyncReadExt};
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -31,6 +32,7 @@ wasmtime::component::bindgen!({
|
||||||
path: "../extension_api/wit/since_v0.2.0",
|
path: "../extension_api/wit/since_v0.2.0",
|
||||||
with: {
|
with: {
|
||||||
"worktree": ExtensionWorktree,
|
"worktree": ExtensionWorktree,
|
||||||
|
"project": ExtensionProject,
|
||||||
"key-value-store": ExtensionKeyValueStore,
|
"key-value-store": ExtensionKeyValueStore,
|
||||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
||||||
},
|
},
|
||||||
|
@ -46,6 +48,10 @@ pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||||
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
||||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||||
|
|
||||||
|
pub struct ExtensionProject {
|
||||||
|
pub worktree_ids: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn linker() -> &'static Linker<WasmState> {
|
pub fn linker() -> &'static Linker<WasmState> {
|
||||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||||
|
@ -69,6 +75,22 @@ impl HostKeyValueStore for WasmState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl HostProject for WasmState {
|
||||||
|
async fn worktree_ids(
|
||||||
|
&mut self,
|
||||||
|
project: Resource<ExtensionProject>,
|
||||||
|
) -> wasmtime::Result<Vec<u64>> {
|
||||||
|
let project = self.table.get(&project)?;
|
||||||
|
Ok(project.worktree_ids.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||||
|
// We only ever hand out borrows of projects.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl HostWorktree for WasmState {
|
impl HostWorktree for WasmState {
|
||||||
async fn id(
|
async fn id(
|
||||||
|
@ -421,14 +443,33 @@ impl ExtensionImports for WasmState {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(serde_json::to_string(&settings::LspSettings {
|
Ok(serde_json::to_string(&settings::LspSettings {
|
||||||
binary: settings.binary.map(|binary| settings::BinarySettings {
|
binary: settings.binary.map(|binary| settings::CommandSettings {
|
||||||
path: binary.path,
|
path: binary.path,
|
||||||
arguments: binary.arguments,
|
arguments: binary.arguments,
|
||||||
|
env: None,
|
||||||
}),
|
}),
|
||||||
settings: settings.settings,
|
settings: settings.settings,
|
||||||
initialization_options: settings.initialization_options,
|
initialization_options: settings.initialization_options,
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
"context_servers" => {
|
||||||
|
let settings = key
|
||||||
|
.and_then(|key| {
|
||||||
|
ContextServerSettings::get(location, cx)
|
||||||
|
.context_servers
|
||||||
|
.get(key.as_str())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||||
|
command: settings.command.map(|command| settings::CommandSettings {
|
||||||
|
path: Some(command.path),
|
||||||
|
arguments: Some(command.args),
|
||||||
|
env: command.env.map(|env| env.into_iter().collect()),
|
||||||
|
}),
|
||||||
|
settings: settings.settings,
|
||||||
|
})?)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
bail!("Unknown settings category: {}", category);
|
bail!("Unknown settings category: {}", category);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use context_servers::manager::{NativeContextServer, ServerConfig};
|
use context_servers::manager::{NativeContextServer, ServerCommand, ServerConfig};
|
||||||
use context_servers::protocol::InitializedContextServerProtocol;
|
use context_servers::protocol::InitializedContextServerProtocol;
|
||||||
use context_servers::ContextServer;
|
use context_servers::ContextServer;
|
||||||
use extension_host::wasm_host::{WasmExtension, WasmHost};
|
use extension_host::wasm_host::{ExtensionProject, WasmExtension, WasmHost};
|
||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
use gpui::AsyncAppContext;
|
use gpui::{AsyncAppContext, Model};
|
||||||
|
use project::Project;
|
||||||
|
use wasmtime_wasi::WasiView as _;
|
||||||
|
|
||||||
pub struct ExtensionContextServer {
|
pub struct ExtensionContextServer {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -20,14 +22,27 @@ pub struct ExtensionContextServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtensionContextServer {
|
impl ExtensionContextServer {
|
||||||
pub async fn new(extension: WasmExtension, host: Arc<WasmHost>, id: Arc<str>) -> Result<Self> {
|
pub async fn new(
|
||||||
|
extension: WasmExtension,
|
||||||
|
host: Arc<WasmHost>,
|
||||||
|
id: Arc<str>,
|
||||||
|
project: Model<Project>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let extension_project = project.update(&mut cx, |project, cx| ExtensionProject {
|
||||||
|
worktree_ids: project
|
||||||
|
.visible_worktrees(cx)
|
||||||
|
.map(|worktree| worktree.read(cx).id().to_proto())
|
||||||
|
.collect(),
|
||||||
|
})?;
|
||||||
let command = extension
|
let command = extension
|
||||||
.call({
|
.call({
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
|extension, store| {
|
|extension, store| {
|
||||||
async move {
|
async move {
|
||||||
|
let project = store.data_mut().table().push(extension_project)?;
|
||||||
let command = extension
|
let command = extension
|
||||||
.call_context_server_command(store, id.clone())
|
.call_context_server_command(store, id.clone(), project)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| anyhow!("{}", e))?;
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
anyhow::Ok(command)
|
anyhow::Ok(command)
|
||||||
|
@ -38,17 +53,19 @@ impl ExtensionContextServer {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let config = Arc::new(ServerConfig {
|
let config = Arc::new(ServerConfig {
|
||||||
id: id.to_string(),
|
settings: None,
|
||||||
executable: command.command,
|
command: Some(ServerCommand {
|
||||||
args: command.args,
|
path: command.command,
|
||||||
env: Some(command.env.into_iter().collect()),
|
args: command.args,
|
||||||
|
env: Some(command.env.into_iter().collect()),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
anyhow::Ok(Self {
|
anyhow::Ok(Self {
|
||||||
extension,
|
extension,
|
||||||
host,
|
host,
|
||||||
id,
|
id: id.clone(),
|
||||||
context_server: Arc::new(NativeContextServer::new(config)),
|
context_server: Arc::new(NativeContextServer::new(id, config)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,14 +84,14 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio
|
||||||
.register_server_factory(
|
.register_server_factory(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
Arc::new({
|
Arc::new({
|
||||||
move |cx| {
|
move |project, cx| {
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let extension = extension.clone();
|
let extension = extension.clone();
|
||||||
let host = host.clone();
|
let host = host.clone();
|
||||||
cx.spawn(|_cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
let context_server =
|
let context_server =
|
||||||
ExtensionContextServer::new(extension, host, id).await?;
|
ExtensionContextServer::new(extension, host, id, project, cx)
|
||||||
|
.await?;
|
||||||
anyhow::Ok(Arc::new(context_server) as _)
|
anyhow::Ok(Arc::new(context_server) as _)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue