debugger: Use DAP schema to configure daps (#30833)

This PR allows DAPs to define their own schema so users can see
completion items when editing their debug.json files.

Users facing this aren’t the biggest chance, but behind the scenes, this
affected a lot of code because we manually translated common fields from
Zed's config format to be adapter-specific. Now we store the raw JSON
from a user's configuration file and just send that.

I'm ignoring the Protobuf CICD error because the DebugTaskDefinition
message is not yet user facing and we need to deprecate some fields in
it.

Release Notes:

- debugger beta: Show completion items when editing debug.json
- debugger beta: Breaking change, debug.json schema now relays on what
DAP you have selected instead of always having the same based values.

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
Anthony Eid 2025-05-22 05:48:26 -04:00 committed by GitHub
parent 0d7f4842f3
commit 1c9b818342
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 2357 additions and 740 deletions

View file

@ -398,6 +398,20 @@ impl extension::Extension for WasmExtension {
})
.await
}
async fn 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 {
@ -710,100 +724,3 @@ impl CacheStore for IncrementalCompilationCache {
true
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use extension::{
ExtensionCapability, ExtensionLibraryKind, LanguageServerManifestEntry, LibManifestEntry,
SchemaVersion,
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
};
use gpui::TestAppContext;
use reqwest_client::ReqwestClient;
use super::*;
#[gpui::test]
fn test_cache_size_for_test_extension(cx: &TestAppContext) {
let cache_store = cache_store();
let engine = wasm_engine();
let wasm_bytes = wasm_bytes(cx, &mut manifest());
Component::new(&engine, wasm_bytes).unwrap();
cache_store.cache.run_pending_tasks();
let size: usize = cache_store
.cache
.iter()
.map(|(k, v)| k.len() + v.len())
.sum();
// If this assertion fails, it means extensions got larger and we may want to
// reconsider our cache size.
assert!(size < 512 * 1024);
}
fn wasm_bytes(cx: &TestAppContext, manifest: &mut ExtensionManifest) -> Vec<u8> {
let extension_builder = extension_builder();
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("extensions/test-extension");
cx.executor()
.block(extension_builder.compile_extension(
&path,
manifest,
CompileExtensionOptions { release: true },
))
.unwrap();
std::fs::read(path.join("extension.wasm")).unwrap()
}
fn extension_builder() -> ExtensionBuilder {
let user_agent = format!(
"Zed Extension CLI/{} ({}; {})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH
);
let http_client = Arc::new(ReqwestClient::user_agent(&user_agent).unwrap());
// Local dir so that we don't have to download it on every run
let build_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("benches/.build");
ExtensionBuilder::new(http_client, build_dir)
}
fn manifest() -> ExtensionManifest {
ExtensionManifest {
id: "test-extension".into(),
name: "Test Extension".into(),
version: "0.1.0".into(),
schema_version: SchemaVersion(1),
description: Some("An extension for use in tests.".into()),
authors: Vec::new(),
repository: None,
themes: Default::default(),
icon_themes: Vec::new(),
lib: LibManifestEntry {
kind: Some(ExtensionLibraryKind::Rust),
version: Some(SemanticVersion::new(0, 1, 0)),
},
languages: Vec::new(),
grammars: BTreeMap::default(),
language_servers: [("gleam".into(), LanguageServerManifestEntry::default())]
.into_iter()
.collect(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: vec![ExtensionCapability::ProcessExec {
command: "echo".into(),
args: vec!["hello!".into()],
}],
debug_adapters: Vec::new(),
}
}
}

View file

@ -922,6 +922,20 @@ impl Extension {
_ => anyhow::bail!("`get_dap_binary` not available prior to v0.6.0"),
}
}
pub async fn call_dap_schema(&self, store: &mut Store<WasmState>) -> Result<String, String> {
match self {
Extension::V0_6_0(ext) => {
let schema = ext
.call_dap_schema(store)
.await
.map_err(|err| err.to_string())?;
schema
}
_ => Err("`get_dap_binary` not available prior to v0.6.0".to_string()),
}
}
}
trait ToWasmtimeResult<T> {

View file

@ -1,7 +1,7 @@
use crate::wasm_host::wit::since_v0_6_0::{
dap::{
AttachRequest, DebugRequest, LaunchRequest, StartDebuggingRequestArguments,
StartDebuggingRequestArgumentsRequest, TcpArguments, TcpArgumentsTemplate,
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, TcpArguments,
TcpArgumentsTemplate,
},
slash_command::SlashCommandOutputSection,
};
@ -79,17 +79,6 @@ impl From<Command> for extension::Command {
}
}
impl From<extension::LaunchRequest> for LaunchRequest {
fn from(value: extension::LaunchRequest) -> Self {
Self {
program: value.program,
cwd: value.cwd.map(|path| path.to_string_lossy().into_owned()),
envs: value.env.into_iter().collect(),
args: value.args,
}
}
}
impl From<StartDebuggingRequestArgumentsRequest>
for extension::StartDebuggingRequestArgumentsRequest
{
@ -129,32 +118,14 @@ impl From<extension::TcpArgumentsTemplate> for TcpArgumentsTemplate {
}
}
}
impl From<extension::AttachRequest> for AttachRequest {
fn from(value: extension::AttachRequest) -> Self {
Self {
process_id: value.process_id,
}
}
}
impl From<extension::DebugRequest> for DebugRequest {
fn from(value: extension::DebugRequest) -> Self {
match value {
extension::DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()),
extension::DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()),
}
}
}
impl TryFrom<extension::DebugTaskDefinition> for DebugTaskDefinition {
type Error = anyhow::Error;
fn try_from(value: extension::DebugTaskDefinition) -> Result<Self, Self::Error> {
let initialize_args = value.initialize_args.map(|s| s.to_string());
Ok(Self {
label: value.label.to_string(),
adapter: value.adapter.to_string(),
request: value.request.into(),
initialize_args,
stop_on_entry: value.stop_on_entry,
config: value.config.to_string(),
tcp_connection: value.tcp_connection.map(Into::into),
})
}