From 8df6ce2aac8597ec0462482199a07134e413b4fe Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:25:32 +0200 Subject: [PATCH] extension: Update DAP extension API (#32448) - DAP schemas will be stored in `debug_adapters_schemas` subdirectory in extension work dir. - Added Debug Config integration and such. Release Notes: - N/A --- Cargo.lock | 1 + crates/dap/src/adapters.rs | 4 +- crates/dap/src/registry.rs | 5 +- crates/dap_adapters/src/codelldb.rs | 2 +- crates/dap_adapters/src/gdb.rs | 2 +- crates/dap_adapters/src/go.rs | 2 +- crates/dap_adapters/src/javascript.rs | 2 +- crates/dap_adapters/src/php.rs | 2 +- crates/dap_adapters/src/python.rs | 2 +- crates/dap_adapters/src/ruby.rs | 2 +- crates/debug_adapter_extension/Cargo.toml | 1 + .../src/debug_adapter_extension.rs | 18 +- .../src/extension_dap_adapter.rs | 27 ++- .../src/extension_locator_adapter.rs | 50 ++++++ crates/extension/src/extension.rs | 26 ++- crates/extension/src/extension_builder.rs | 20 ++- crates/extension/src/extension_host_proxy.rs | 9 + crates/extension/src/extension_manifest.rs | 4 + crates/extension/src/types/dap.rs | 5 +- crates/extension_api/src/extension_api.rs | 70 +++++++- crates/extension_api/wit/since_v0.6.0/dap.wit | 65 ++++++- .../wit/since_v0.6.0/extension.wit | 9 +- .../extension_compilation_benchmark.rs | 1 + crates/extension_host/src/extension_host.rs | 5 + .../src/extension_store_test.rs | 3 + crates/extension_host/src/wasm_host.rs | 70 +++++++- crates/extension_host/src/wasm_host/wit.rs | 90 +++++++++- .../src/wasm_host/wit/since_v0_6_0.rs | 170 +++++++++++++++++- 28 files changed, 620 insertions(+), 47 deletions(-) create mode 100644 crates/debug_adapter_extension/src/extension_locator_adapter.rs diff --git a/Cargo.lock b/Cargo.lock index 94c20b4913..4880424b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4242,6 +4242,7 @@ dependencies = [ "gpui", "serde_json", "task", + "util", "workspace-hack", ] diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 7085e8fc15..57775214b8 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -368,7 +368,7 @@ pub trait DebugAdapter: 'static + Send + Sync { } } - async fn dap_schema(&self) -> serde_json::Value; + fn dap_schema(&self) -> serde_json::Value; fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option { None @@ -394,7 +394,7 @@ impl DebugAdapter for FakeAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { serde_json::Value::Null } diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index e540cc7294..30b6a94874 100644 --- a/crates/dap/src/registry.rs +++ b/crates/dap/src/registry.rs @@ -14,7 +14,7 @@ use crate::{ }; use std::{collections::BTreeMap, sync::Arc}; -/// Given a user build configuration, locator creates a fill-in debug target ([DebugRequest]) on behalf of the user. +/// Given a user build configuration, locator creates a fill-in debug target ([DebugScenario]) on behalf of the user. #[async_trait] pub trait DapLocator: Send + Sync { fn name(&self) -> SharedString; @@ -67,13 +67,12 @@ impl DapRegistry { pub async fn adapters_schema(&self) -> task::AdapterSchemas { let mut schemas = AdapterSchemas(vec![]); - // Clone to avoid holding lock over await points let adapters = self.0.read().adapters.clone(); for (name, adapter) in adapters.into_iter() { schemas.0.push(AdapterSchema { adapter: name.into(), - schema: adapter.dap_schema().await, + schema: adapter.dap_schema(), }); } diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 2c23a36779..1f92a0b2e4 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -133,7 +133,7 @@ impl DebugAdapter for CodeLldbDebugAdapter { }) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "properties": { "request": { diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 83e552d7c1..502fce15a2 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -63,7 +63,7 @@ impl DebugAdapter for GdbDebugAdapter { }) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "oneOf": [ { diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 727efdc6fc..bcc1eb22b5 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -95,7 +95,7 @@ impl DebugAdapter for GoDebugAdapter { Some(SharedString::new_static("Go").into()) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { // Create common properties shared between launch and attach let common_properties = json!({ "debugAdapter": { diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 8981eade61..1ef85e4eee 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -182,7 +182,7 @@ impl DebugAdapter for JsDebugAdapter { }) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "oneOf": [ { diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 2c65b8868e..9d60e60608 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -110,7 +110,7 @@ impl PhpDebugAdapter { #[async_trait(?Send)] impl DebugAdapter for PhpDebugAdapter { - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "properties": { "request": { diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 113c183cff..72d802825f 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -257,7 +257,7 @@ impl DebugAdapter for PythonDebugAdapter { }) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "properties": { "request": { diff --git a/crates/dap_adapters/src/ruby.rs b/crates/dap_adapters/src/ruby.rs index 47ed975909..24582e1b6d 100644 --- a/crates/dap_adapters/src/ruby.rs +++ b/crates/dap_adapters/src/ruby.rs @@ -49,7 +49,7 @@ impl DebugAdapter for RubyDebugAdapter { Ok(StartDebuggingRequestArgumentsRequest::Launch) } - async fn dap_schema(&self) -> serde_json::Value { + fn dap_schema(&self) -> serde_json::Value { json!({ "type": "object", "properties": { diff --git a/crates/debug_adapter_extension/Cargo.toml b/crates/debug_adapter_extension/Cargo.toml index 15599e0a93..78d7cbaba3 100644 --- a/crates/debug_adapter_extension/Cargo.toml +++ b/crates/debug_adapter_extension/Cargo.toml @@ -12,6 +12,7 @@ dap.workspace = true extension.workspace = true gpui.workspace = true serde_json.workspace = true +util.workspace = true task.workspace = true workspace-hack = { version = "0.1", path = "../../tooling/workspace-hack" } diff --git a/crates/debug_adapter_extension/src/debug_adapter_extension.rs b/crates/debug_adapter_extension/src/debug_adapter_extension.rs index 1c7add7737..22ba9175b2 100644 --- a/crates/debug_adapter_extension/src/debug_adapter_extension.rs +++ b/crates/debug_adapter_extension/src/debug_adapter_extension.rs @@ -1,4 +1,5 @@ mod extension_dap_adapter; +mod extension_locator_adapter; use std::sync::Arc; @@ -6,6 +7,9 @@ use dap::DapRegistry; use extension::{ExtensionDebugAdapterProviderProxy, ExtensionHostProxy}; use extension_dap_adapter::ExtensionDapAdapter; use gpui::App; +use util::ResultExt; + +use crate::extension_locator_adapter::ExtensionLocatorAdapter; pub fn init(extension_host_proxy: Arc, cx: &mut App) { let language_server_registry_proxy = DebugAdapterRegistryProxy::new(cx); @@ -30,11 +34,21 @@ impl ExtensionDebugAdapterProviderProxy for DebugAdapterRegistryProxy { &self, extension: Arc, debug_adapter_name: Arc, + ) { + if let Some(adapter) = ExtensionDapAdapter::new(extension, debug_adapter_name).log_err() { + self.debug_adapter_registry.add_adapter(Arc::new(adapter)); + } + } + + fn register_debug_locator( + &self, + extension: Arc, + locator_name: Arc, ) { self.debug_adapter_registry - .add_adapter(Arc::new(ExtensionDapAdapter::new( + .add_locator(Arc::new(ExtensionLocatorAdapter::new( extension, - debug_adapter_name, + locator_name, ))); } } diff --git a/crates/debug_adapter_extension/src/extension_dap_adapter.rs b/crates/debug_adapter_extension/src/extension_dap_adapter.rs index 4099c86709..3878431056 100644 --- a/crates/debug_adapter_extension/src/extension_dap_adapter.rs +++ b/crates/debug_adapter_extension/src/extension_dap_adapter.rs @@ -1,6 +1,10 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; -use anyhow::Result; +use anyhow::{Context, Result}; use async_trait::async_trait; use dap::adapters::{ DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, @@ -12,17 +16,26 @@ use task::{DebugScenario, ZedDebugConfig}; pub(crate) struct ExtensionDapAdapter { extension: Arc, debug_adapter_name: Arc, + schema: serde_json::Value, } impl ExtensionDapAdapter { pub(crate) fn new( extension: Arc, debug_adapter_name: Arc, - ) -> Self { - Self { + ) -> Result { + let schema = std::fs::read_to_string(extension.path_from_extension( + &Path::new("debug_adapter_schemas").join(debug_adapter_name.as_ref()), + )) + .with_context(|| format!("Failed to read debug adapter schema for {debug_adapter_name}"))?; + let schema = serde_json::Value::from_str(&schema).with_context(|| { + format!("Debug adapter schema for {debug_adapter_name} is not a valid JSON") + })?; + Ok(Self { extension, debug_adapter_name, - } + schema, + }) } } @@ -61,8 +74,8 @@ impl DebugAdapter for ExtensionDapAdapter { self.debug_adapter_name.as_ref().into() } - async fn dap_schema(&self) -> serde_json::Value { - self.extension.get_dap_schema().await.unwrap_or_default() + fn dap_schema(&self) -> serde_json::Value { + self.schema.clone() } async fn get_binary( diff --git a/crates/debug_adapter_extension/src/extension_locator_adapter.rs b/crates/debug_adapter_extension/src/extension_locator_adapter.rs new file mode 100644 index 0000000000..54c03b1eaf --- /dev/null +++ b/crates/debug_adapter_extension/src/extension_locator_adapter.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use async_trait::async_trait; +use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName}; +use extension::Extension; +use gpui::SharedString; +use std::sync::Arc; +use task::{DebugScenario, SpawnInTerminal, TaskTemplate}; + +pub(crate) struct ExtensionLocatorAdapter { + extension: Arc, + locator_name: SharedString, +} + +impl ExtensionLocatorAdapter { + pub(crate) fn new(extension: Arc, locator_name: Arc) -> Self { + Self { + extension, + locator_name: SharedString::from(locator_name), + } + } +} + +#[async_trait] +impl DapLocator for ExtensionLocatorAdapter { + fn name(&self) -> SharedString { + self.locator_name.clone() + } + /// Determines whether this locator can generate debug target for given task. + async fn create_scenario( + &self, + build_config: &TaskTemplate, + resolved_label: &str, + adapter: &DebugAdapterName, + ) -> Option { + self.extension + .dap_locator_create_scenario( + self.locator_name.as_ref().to_owned(), + build_config.clone(), + resolved_label.to_owned(), + adapter.0.as_ref().to_owned(), + ) + .await + .ok() + .flatten() + } + + async fn run(&self, _build_config: SpawnInTerminal) -> Result { + Err(anyhow::anyhow!("Not implemented")) + } +} diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index 77b2cf699d..1303dc6540 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -14,6 +14,7 @@ use fs::normalize_path; use gpui::{App, Task}; use language::LanguageName; use semantic_version::SemanticVersion; +use task::{SpawnInTerminal, ZedDebugConfig}; pub use crate::extension_events::*; pub use crate::extension_host_proxy::*; @@ -144,7 +145,30 @@ pub trait Extension: Send + Sync + 'static { worktree: Arc, ) -> Result; - async fn get_dap_schema(&self) -> Result; + async fn dap_request_kind( + &self, + dap_name: Arc, + config: serde_json::Value, + ) -> Result; + + async fn dap_config_to_scenario( + &self, + config: ZedDebugConfig, + worktree: Arc, + ) -> Result; + + async fn dap_locator_create_scenario( + &self, + locator_name: String, + build_config_template: BuildTaskTemplate, + resolved_label: String, + debug_adapter_name: String, + ) -> Result>; + async fn run_dap_locator( + &self, + locator_name: String, + config: SpawnInTerminal, + ) -> Result; } pub fn parse_wasm_extension_version( diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 47fefe9d07..76ab5492a7 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -1,7 +1,7 @@ use crate::{ ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version, }; -use anyhow::{Context as _, Result, bail}; +use anyhow::{Context as _, Result, bail, ensure}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use futures::io::BufReader; @@ -12,6 +12,7 @@ use std::{ env, fs, mem, path::{Path, PathBuf}, process::Stdio, + str::FromStr, sync::Arc, }; use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _}; @@ -97,6 +98,23 @@ impl ExtensionBuilder { log::info!("compiled Rust extension {}", extension_dir.display()); } + let debug_adapters_dir = extension_dir.join("debug_adapter_schemas"); + if !extension_manifest.debug_adapters.is_empty() { + ensure!( + debug_adapters_dir.exists(), + "Expected debug adapter schemas directory to exist" + ); + } + for debug_adapter_name in &extension_manifest.debug_adapters { + let debug_adapter_schema_path = debug_adapters_dir.join(debug_adapter_name.as_ref()); + let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path) + .with_context(|| { + format!("failed to read debug adapter schema for `{debug_adapter_name}`") + })?; + _ = serde_json::Value::from_str(&debug_adapter_schema).with_context(|| { + format!("Debug adapter schema for `{debug_adapter_name}` is not a valid JSON") + })?; + } for (grammar_name, grammar_metadata) in &extension_manifest.grammars { let snake_cased_grammar_name = grammar_name.to_snake_case(); if grammar_name.as_ref() != snake_cased_grammar_name.as_str() { diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs index a91c1fca75..a288da3263 100644 --- a/crates/extension/src/extension_host_proxy.rs +++ b/crates/extension/src/extension_host_proxy.rs @@ -412,6 +412,7 @@ impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy { pub trait ExtensionDebugAdapterProviderProxy: Send + Sync + 'static { fn register_debug_adapter(&self, extension: Arc, debug_adapter_name: Arc); + fn register_debug_locator(&self, extension: Arc, locator_name: Arc); } impl ExtensionDebugAdapterProviderProxy for ExtensionHostProxy { @@ -422,4 +423,12 @@ impl ExtensionDebugAdapterProviderProxy for ExtensionHostProxy { proxy.register_debug_adapter(extension, debug_adapter_name) } + + fn register_debug_locator(&self, extension: Arc, locator_name: Arc) { + let Some(proxy) = self.debug_adapter_provider_proxy.read().clone() else { + return; + }; + + proxy.register_debug_locator(extension, locator_name) + } } diff --git a/crates/extension/src/extension_manifest.rs b/crates/extension/src/extension_manifest.rs index 7a432c7fc2..d7e8c149c3 100644 --- a/crates/extension/src/extension_manifest.rs +++ b/crates/extension/src/extension_manifest.rs @@ -89,6 +89,8 @@ pub struct ExtensionManifest { pub capabilities: Vec, #[serde(default)] pub debug_adapters: Vec>, + #[serde(default)] + pub debug_locators: Vec>, } impl ExtensionManifest { @@ -277,6 +279,7 @@ fn manifest_from_old_manifest( snippets: None, capabilities: Vec::new(), debug_adapters: vec![], + debug_locators: vec![], } } @@ -305,6 +308,7 @@ mod tests { snippets: None, capabilities: vec![], debug_adapters: Default::default(), + debug_locators: Default::default(), } } diff --git a/crates/extension/src/types/dap.rs b/crates/extension/src/types/dap.rs index 98e0a3db36..5dcd7f57d5 100644 --- a/crates/extension/src/types/dap.rs +++ b/crates/extension/src/types/dap.rs @@ -2,4 +2,7 @@ pub use dap::{ StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, adapters::{DebugAdapterBinary, DebugTaskDefinition, TcpArguments}, }; -pub use task::{AttachRequest, DebugRequest, LaunchRequest, TcpArgumentsTemplate}; +pub use task::{ + AttachRequest, BuildTaskDefinition, DebugRequest, DebugScenario, LaunchRequest, + TaskTemplate as BuildTaskTemplate, TcpArgumentsTemplate, +}; diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index 3137a60dcc..1ff7adf2c3 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -20,8 +20,8 @@ pub use wit::{ make_file_executable, zed::extension::context_server::ContextServerConfiguration, zed::extension::dap::{ - DebugAdapterBinary, DebugTaskDefinition, StartDebuggingRequestArguments, - StartDebuggingRequestArgumentsRequest, TcpArguments, TcpArgumentsTemplate, + DebugAdapterBinary, DebugRequest, DebugTaskDefinition, StartDebuggingRequestArguments, + StartDebuggingRequestArgumentsRequest, TaskTemplate, TcpArguments, TcpArgumentsTemplate, resolve_tcp_template, }, zed::extension::github::{ @@ -204,8 +204,35 @@ pub trait Extension: Send + Sync { Err("`get_dap_binary` not implemented".to_string()) } - fn dap_schema(&mut self) -> Result { - Err("`dap_schema` not implemented".to_string()) + fn dap_request_kind( + &mut self, + _adapter_name: String, + _config: serde_json::Value, + ) -> Result { + Err("`dap_request_kind` not implemented".to_string()) + } + fn dap_config_to_scenario( + &mut self, + _adapter_name: DebugConfig, + _config: &Worktree, + ) -> Result { + Err("`dap_config_to_scenario` not implemented".to_string()) + } + fn dap_locator_create_scenario( + &mut self, + _locator_name: String, + _build_task: TaskTemplate, + _resolved_label: String, + _debug_adapter_name: String, + ) -> Option { + None + } + fn run_dap_locator( + &mut self, + _locator_name: String, + _build_task: TaskTemplate, + ) -> Result { + Err("`run_dap_locator` not implemented".to_string()) } } @@ -401,8 +428,39 @@ impl wit::Guest for Component { extension().get_dap_binary(adapter_name, config, user_installed_path, worktree) } - fn dap_schema() -> Result { - extension().dap_schema().map(|schema| schema.to_string()) + fn dap_request_kind( + adapter_name: String, + config: String, + ) -> Result { + extension().dap_request_kind( + adapter_name, + serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?, + ) + } + fn dap_config_to_scenario( + config: DebugConfig, + worktree: &Worktree, + ) -> Result { + extension().dap_config_to_scenario(config, worktree) + } + fn dap_locator_create_scenario( + locator_name: String, + build_task: TaskTemplate, + resolved_label: String, + debug_adapter_name: String, + ) -> Option { + extension().dap_locator_create_scenario( + locator_name, + build_task, + resolved_label, + debug_adapter_name, + ) + } + fn run_dap_locator( + locator_name: String, + build_task: TaskTemplate, + ) -> Result { + extension().run_dap_locator(locator_name, build_task) } } diff --git a/crates/extension_api/wit/since_v0.6.0/dap.wit b/crates/extension_api/wit/since_v0.6.0/dap.wit index f49e6be525..a3f07435d1 100644 --- a/crates/extension_api/wit/since_v0.6.0/dap.wit +++ b/crates/extension_api/wit/since_v0.6.0/dap.wit @@ -32,10 +32,55 @@ interface dap { timeout: option, } - record debug-task-definition { + /// Debug Config is the "highest-level" configuration for a debug session. + /// It comes from a new session modal UI; thus, it is essentially debug-adapter-agnostic. + /// It is expected of the extension to translate this generic configuration into something that can be debugged by the adapter (debug scenario). + record debug-config { + /// Name of the debug task label: string, + /// The debug adapter to use adapter: string, + request: debug-request, + stop-on-entry: option, + } + + record task-template { + /// Human readable name of the task to display in the UI. + label: string, + /// Executable command to spawn. + command: string, + args: list, + env: env-vars, + cwd: option, + } + + /// A task template with substituted task variables. + type resolved-task = task-template; + + /// A task template for building a debug target. + type build-task-template = task-template; + + variant build-task-definition { + by-name(string), + template(build-task-definition-template-payload ) + } + record build-task-definition-template-payload { + locator-name: option, + template: build-task-template + } + + /// Debug Scenario is the user-facing configuration type (used in debug.json). It is still concerned with what to debug and not necessarily how to do it (except for any + /// debug-adapter-specific configuration options). + record debug-scenario { + /// Unsubstituted label for the task.DebugAdapterBinary + label: string, + /// Name of the Debug Adapter this configuration is intended for. + adapter: string, + /// An optional build step to be ran prior to starting a debug session. Build steps are used by Zed's locators to locate the executable to debug. + build: option, + /// JSON-encoded configuration for a given debug adapter. config: string, + /// TCP connection parameters (if they were specified by user) tcp-connection: option, } @@ -44,16 +89,34 @@ interface dap { attach, } + record debug-task-definition { + /// Unsubstituted label for the task.DebugAdapterBinary + label: string, + /// Name of the Debug Adapter this configuration is intended for. + adapter: string, + /// JSON-encoded configuration for a given debug adapter. + config: string, + /// TCP connection parameters (if they were specified by user) + tcp-connection: option, + } + record start-debugging-request-arguments { + /// JSON-encoded configuration for a given debug adapter. It is specific to each debug adapter. + /// `configuration` will have it's Zed variable references substituted prior to being passed to the debug adapter. configuration: string, request: start-debugging-request-arguments-request, } + /// The lowest-level representation of a debug session, which specifies: + /// - How to start a debug adapter process + /// - How to start a debug session with it (using DAP protocol) + /// for a given debug scenario. record debug-adapter-binary { command: option, arguments: list, envs: env-vars, cwd: option, + /// Zed will use TCP transport if `connection` is specified. connection: option, request-args: start-debugging-request-arguments } diff --git a/crates/extension_api/wit/since_v0.6.0/extension.wit b/crates/extension_api/wit/since_v0.6.0/extension.wit index e66476e3ce..201eb30764 100644 --- a/crates/extension_api/wit/since_v0.6.0/extension.wit +++ b/crates/extension_api/wit/since_v0.6.0/extension.wit @@ -11,7 +11,7 @@ world extension { use common.{env-vars, range}; use context-server.{context-server-configuration}; - use dap.{debug-adapter-binary, debug-task-definition, debug-request}; + use dap.{attach-request, build-task-template, debug-config, debug-adapter-binary, debug-task-definition, debug-request, debug-scenario, launch-request, resolved-task, start-debugging-request-arguments-request}; use lsp.{completion, symbol}; use process.{command}; use slash-command.{slash-command, slash-command-argument-completion, slash-command-output}; @@ -159,6 +159,9 @@ world extension { /// Returns a configured debug adapter binary for a given debug task. export get-dap-binary: func(adapter-name: string, config: debug-task-definition, user-installed-path: option, worktree: borrow) -> result; - /// Get a debug adapter's configuration schema - export dap-schema: func() -> result; + /// Returns the kind of a debug scenario (launch or attach). + export dap-request-kind: func(adapter-name: string, config: string) -> result; + export dap-config-to-scenario: func(config: debug-config, worktree: borrow) -> result; + export dap-locator-create-scenario: func(locator-name: string, build-config-template: build-task-template, resolved-label: string, debug-adapter-name: string) -> option; + export run-dap-locator: func(locator-name: string, config: resolved-task) -> result; } diff --git a/crates/extension_host/benches/extension_compilation_benchmark.rs b/crates/extension_host/benches/extension_compilation_benchmark.rs index 24a77b0796..9d867af041 100644 --- a/crates/extension_host/benches/extension_compilation_benchmark.rs +++ b/crates/extension_host/benches/extension_compilation_benchmark.rs @@ -139,6 +139,7 @@ fn manifest() -> ExtensionManifest { args: vec!["hello!".into()], }], debug_adapters: Default::default(), + debug_locators: Default::default(), } } diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 4e6b624d49..f6ea9e6e7f 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1348,6 +1348,11 @@ impl ExtensionStore { this.proxy .register_debug_adapter(extension.clone(), debug_adapter.clone()); } + + for debug_adapter in &manifest.debug_adapters { + this.proxy + .register_debug_locator(extension.clone(), debug_adapter.clone()); + } } this.wasm_extensions.extend(wasm_extensions); diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index fd91080a5c..b640fe468b 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -163,6 +163,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { snippets: None, capabilities: Vec::new(), debug_adapters: Default::default(), + debug_locators: Default::default(), }), dev: false, }, @@ -193,6 +194,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { snippets: None, capabilities: Vec::new(), debug_adapters: Default::default(), + debug_locators: Default::default(), }), dev: false, }, @@ -368,6 +370,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { snippets: None, capabilities: Vec::new(), debug_adapters: Default::default(), + debug_locators: Default::default(), }), dev: false, }, diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index d788cdc5b6..a4747c8005 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -3,6 +3,7 @@ pub mod wit; use crate::ExtensionManifest; use anyhow::{Context as _, Result, anyhow, bail}; use async_trait::async_trait; +use dap::{DebugRequest, StartDebuggingRequestArgumentsRequest}; use extension::{ CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary, DebugTaskDefinition, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, @@ -32,6 +33,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; +use task::{DebugScenario, SpawnInTerminal, TaskTemplate, ZedDebugConfig}; use wasmtime::{ CacheStore, Engine, Store, component::{Component, ResourceTable}, @@ -399,14 +401,76 @@ impl extension::Extension for WasmExtension { }) .await } + async fn dap_request_kind( + &self, + dap_name: Arc, + config: serde_json::Value, + ) -> Result { + self.call(|extension, store| { + async move { + let kind = extension + .call_dap_request_kind(store, dap_name, config) + .await? + .map_err(|err| store.data().extension_error(err))?; + Ok(kind.into()) + } + .boxed() + }) + .await + } - async fn get_dap_schema(&self) -> Result { + async fn dap_config_to_scenario( + &self, + config: ZedDebugConfig, + worktree: Arc, + ) -> Result { + self.call(|extension, store| { + async move { + let resource = store.data_mut().table().push(worktree)?; + let kind = extension + .call_dap_config_to_scenario(store, config, resource) + .await? + .map_err(|err| store.data().extension_error(err))?; + Ok(kind) + } + .boxed() + }) + .await + } + + async fn dap_locator_create_scenario( + &self, + locator_name: String, + build_config_template: TaskTemplate, + resolved_label: String, + debug_adapter_name: String, + ) -> Result> { self.call(|extension, store| { async move { extension - .call_dap_schema(store) + .call_dap_locator_create_scenario( + store, + locator_name, + build_config_template, + resolved_label, + debug_adapter_name, + ) .await - .and_then(|schema| serde_json::to_value(schema).map_err(|err| err.to_string())) + } + .boxed() + }) + .await + } + async fn run_dap_locator( + &self, + locator_name: String, + config: SpawnInTerminal, + ) -> Result { + self.call(|extension, store| { + async move { + extension + .call_run_dap_locator(store, locator_name, config) + .await? .map_err(|err| store.data().extension_error(err)) } .boxed() diff --git a/crates/extension_host/src/wasm_host/wit.rs b/crates/extension_host/src/wasm_host/wit.rs index df6844ecd4..8bf7e31bf8 100644 --- a/crates/extension_host/src/wasm_host/wit.rs +++ b/crates/extension_host/src/wasm_host/wit.rs @@ -7,10 +7,14 @@ mod since_v0_3_0; mod since_v0_4_0; mod since_v0_5_0; mod since_v0_6_0; +use dap::DebugRequest; use extension::{DebugTaskDefinition, KeyValueStoreDelegate, WorktreeDelegate}; use language::LanguageName; use lsp::LanguageServerName; use release_channel::ReleaseChannel; +use task::{DebugScenario, SpawnInTerminal, TaskTemplate, ZedDebugConfig}; + +use crate::wasm_host::wit::since_v0_6_0::dap::StartDebuggingRequestArgumentsRequest; use super::{WasmState, wasm_engine}; use anyhow::{Context as _, Result, anyhow}; @@ -922,18 +926,88 @@ impl Extension { _ => anyhow::bail!("`get_dap_binary` not available prior to v0.6.0"), } } - - pub async fn call_dap_schema(&self, store: &mut Store) -> Result { + pub async fn call_dap_request_kind( + &self, + store: &mut Store, + adapter_name: Arc, + config: serde_json::Value, + ) -> Result> { match self { Extension::V0_6_0(ext) => { - let schema = ext - .call_dap_schema(store) - .await - .map_err(|err| err.to_string())?; + let config = + serde_json::to_string(&config).context("Adapter config is not a valid JSON")?; + let dap_binary = ext + .call_dap_request_kind(store, &adapter_name, &config) + .await? + .map_err(|e| anyhow!("{e:?}"))?; - schema + Ok(Ok(dap_binary)) } - _ => Err("`get_dap_binary` not available prior to v0.6.0".to_string()), + _ => anyhow::bail!("`dap_request_kind` not available prior to v0.6.0"), + } + } + pub async fn call_dap_config_to_scenario( + &self, + store: &mut Store, + config: ZedDebugConfig, + resource: Resource>, + ) -> Result> { + match self { + Extension::V0_6_0(ext) => { + let config = config.into(); + let dap_binary = ext + .call_dap_config_to_scenario(store, &config, resource) + .await? + .map_err(|e| anyhow!("{e:?}"))?; + + Ok(Ok(dap_binary.try_into()?)) + } + _ => anyhow::bail!("`dap_config_to_scenario` not available prior to v0.6.0"), + } + } + pub async fn call_dap_locator_create_scenario( + &self, + store: &mut Store, + locator_name: String, + build_config_template: TaskTemplate, + resolved_label: String, + debug_adapter_name: String, + ) -> Result> { + match self { + Extension::V0_6_0(ext) => { + let build_config_template = build_config_template.into(); + let dap_binary = ext + .call_dap_locator_create_scenario( + store, + &locator_name, + &build_config_template, + &resolved_label, + &debug_adapter_name, + ) + .await?; + + Ok(dap_binary.map(TryInto::try_into).transpose()?) + } + _ => anyhow::bail!("`dap_locator_create_scenario` not available prior to v0.6.0"), + } + } + pub async fn call_run_dap_locator( + &self, + store: &mut Store, + locator_name: String, + resolved_build_task: SpawnInTerminal, + ) -> Result> { + match self { + Extension::V0_6_0(ext) => { + let build_config_template = resolved_build_task.into(); + let dap_request = ext + .call_run_dap_locator(store, &locator_name, &build_config_template) + .await? + .map_err(|e| anyhow!("{e:?}"))?; + + Ok(Ok(dap_request.into())) + } + _ => anyhow::bail!("`dap_locator_create_scenario` not available prior to v0.6.0"), } } } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 7abc528c4e..854ad460e5 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -1,7 +1,7 @@ use crate::wasm_host::wit::since_v0_6_0::{ dap::{ - StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, TcpArguments, - TcpArgumentsTemplate, + AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, LaunchRequest, + StartDebuggingRequestArguments, TcpArguments, TcpArgumentsTemplate, }, slash_command::SlashCommandOutputSection, }; @@ -18,6 +18,7 @@ use extension::{ }; use futures::{AsyncReadExt, lock::Mutex}; use futures::{FutureExt as _, io::BufReader}; +use gpui::SharedString; use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings}; use project::project_settings::ProjectSettings; use semantic_version::SemanticVersion; @@ -25,8 +26,10 @@ use std::{ env, net::Ipv4Addr, path::{Path, PathBuf}, + str::FromStr, sync::{Arc, OnceLock}, }; +use task::{SpawnInTerminal, ZedDebugConfig}; use util::{archive::extract_zip, maybe}; use wasmtime::component::{Linker, Resource}; @@ -119,6 +122,16 @@ impl From for TcpArgumentsTemplate { } } +impl From for extension::TcpArgumentsTemplate { + fn from(value: TcpArgumentsTemplate) -> Self { + Self { + host: value.host.map(Ipv4Addr::from_bits), + port: value.port, + timeout: value.timeout, + } + } +} + impl TryFrom for DebugTaskDefinition { type Error = anyhow::Error; fn try_from(value: extension::DebugTaskDefinition) -> Result { @@ -131,6 +144,71 @@ impl TryFrom for DebugTaskDefinition { } } +impl From for DebugRequest { + fn from(value: task::DebugRequest) -> Self { + match value { + task::DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()), + task::DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()), + } + } +} + +impl From for task::DebugRequest { + fn from(value: DebugRequest) -> Self { + match value { + DebugRequest::Launch(launch_request) => Self::Launch(launch_request.into()), + DebugRequest::Attach(attach_request) => Self::Attach(attach_request.into()), + } + } +} + +impl From for LaunchRequest { + fn from(value: task::LaunchRequest) -> Self { + Self { + program: value.program, + cwd: value.cwd.map(|p| p.to_string_lossy().into_owned()), + args: value.args, + envs: value.env.into_iter().collect(), + } + } +} + +impl From for AttachRequest { + fn from(value: task::AttachRequest) -> Self { + Self { + process_id: value.process_id, + } + } +} + +impl From for task::LaunchRequest { + fn from(value: LaunchRequest) -> Self { + Self { + program: value.program, + cwd: value.cwd.map(|p| p.into()), + args: value.args, + env: value.envs.into_iter().collect(), + } + } +} +impl From for task::AttachRequest { + fn from(value: AttachRequest) -> Self { + Self { + process_id: value.process_id, + } + } +} + +impl From for DebugConfig { + fn from(value: ZedDebugConfig) -> Self { + Self { + label: value.label.into(), + adapter: value.adapter.into(), + request: value.request.into(), + stop_on_entry: value.stop_on_entry, + } + } +} impl TryFrom for extension::DebugAdapterBinary { type Error = anyhow::Error; fn try_from(value: DebugAdapterBinary) -> Result { @@ -145,6 +223,94 @@ impl TryFrom for extension::DebugAdapterBinary { } } +impl From for extension::BuildTaskDefinition { + fn from(value: BuildTaskDefinition) -> Self { + match value { + BuildTaskDefinition::ByName(name) => Self::ByName(name.into()), + BuildTaskDefinition::Template(build_task_template) => Self::Template { + task_template: build_task_template.template.into(), + locator_name: build_task_template.locator_name.map(SharedString::from), + }, + } + } +} + +impl From for BuildTaskDefinition { + fn from(value: extension::BuildTaskDefinition) -> Self { + match value { + extension::BuildTaskDefinition::ByName(name) => Self::ByName(name.into()), + extension::BuildTaskDefinition::Template { + task_template, + locator_name, + } => Self::Template(BuildTaskDefinitionTemplatePayload { + template: task_template.into(), + locator_name: locator_name.map(String::from), + }), + } + } +} +impl From for extension::BuildTaskTemplate { + fn from(value: BuildTaskTemplate) -> Self { + Self { + label: value.label, + command: value.command, + args: value.args, + env: value.env.into_iter().collect(), + cwd: value.cwd, + ..Default::default() + } + } +} +impl From for BuildTaskTemplate { + fn from(value: extension::BuildTaskTemplate) -> Self { + Self { + label: value.label, + command: value.command, + args: value.args, + env: value.env.into_iter().collect(), + cwd: value.cwd, + } + } +} + +impl TryFrom for extension::DebugScenario { + type Error = anyhow::Error; + + fn try_from(value: DebugScenario) -> std::result::Result { + Ok(Self { + adapter: value.adapter.into(), + label: value.label.into(), + build: value.build.map(Into::into), + config: serde_json::Value::from_str(&value.config)?, + tcp_connection: value.tcp_connection.map(Into::into), + }) + } +} + +impl From for DebugScenario { + fn from(value: extension::DebugScenario) -> Self { + Self { + adapter: value.adapter.into(), + label: value.label.into(), + build: value.build.map(Into::into), + config: value.config.to_string(), + tcp_connection: value.tcp_connection.map(Into::into), + } + } +} + +impl From for ResolvedTask { + fn from(value: SpawnInTerminal) -> Self { + Self { + label: value.label, + command: value.command, + args: value.args, + env: value.env.into_iter().collect(), + cwd: value.cwd.map(|s| s.to_string_lossy().into_owned()), + } + } +} + impl From for extension::CodeLabel { fn from(value: CodeLabel) -> Self { Self {