From c4277681d1d6246899e9f810ceb594bafc2f64a8 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 11 Jun 2025 16:48:44 -0400 Subject: [PATCH] debugger: Fix issues with launch.json handling (#32563) After this PR we can run all the in-tree launch.json examples from [this repo](https://github.com/microsoft/vscode-recipes). Things done: - Fill in default cwd at a lower level for all adapters - Update launch.json parsing for DebugScenario changes - Imitate how VS Code normalizes the `type` field for JS debug tasks - Make version field optional - Extend the variable replacer a bit Release Notes: - Debugger Beta: fixed issues preventing loading and running of debug tasks from VS Code's launch.json. --------- Co-authored-by: Anthony Eid Co-authored-by: Anthony Co-authored-by: Conrad Irwin --- crates/dap_adapters/src/codelldb.rs | 17 +++++--- crates/dap_adapters/src/gdb.rs | 15 ++++--- crates/dap_adapters/src/go.rs | 9 +++- crates/dap_adapters/src/javascript.rs | 30 ++++++++++++- crates/dap_adapters/src/php.rs | 8 +++- crates/dap_adapters/src/python.rs | 8 +++- crates/dap_adapters/src/ruby.rs | 9 +++- crates/task/src/vscode_debug_format.rs | 59 ++++++++++++++------------ 8 files changed, 111 insertions(+), 44 deletions(-) diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 156f0d85e1..2c23a36779 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -21,18 +21,21 @@ impl CodeLldbDebugAdapter { fn request_args( &self, + delegate: &Arc, task_definition: &DebugTaskDefinition, ) -> Result { // CodeLLDB uses `name` for a terminal label. let mut configuration = task_definition.config.clone(); - configuration + let obj = configuration .as_object_mut() - .context("CodeLLDB is not a valid json object")? - .insert( - "name".into(), - Value::String(String::from(task_definition.label.as_ref())), - ); + .context("CodeLLDB is not a valid json object")?; + + obj.entry("name") + .or_insert(Value::String(String::from(task_definition.label.as_ref()))); + + obj.entry("cwd") + .or_insert(delegate.worktree_root_path().to_string_lossy().into()); let request = self.request_kind(&configuration)?; @@ -365,7 +368,7 @@ impl DebugAdapter for CodeLldbDebugAdapter { "--settings".into(), json!({"sourceLanguages": ["cpp", "rust"]}).to_string(), ], - request_args: self.request_args(&config)?, + request_args: self.request_args(delegate, &config)?, envs: HashMap::default(), connection: None, }) diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 1c458bd9ca..83e552d7c1 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -177,10 +177,12 @@ impl DebugAdapter for GdbDebugAdapter { let gdb_path = user_setting_path.unwrap_or(gdb_path?); - let request_args = StartDebuggingRequestArguments { - request: self.request_kind(&config.config)?, - configuration: config.config.clone(), - }; + let mut configuration = config.config.clone(); + if let Some(configuration) = configuration.as_object_mut() { + configuration + .entry("cwd") + .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + } Ok(DebugAdapterBinary { command: Some(gdb_path), @@ -188,7 +190,10 @@ impl DebugAdapter for GdbDebugAdapter { envs: HashMap::default(), cwd: Some(delegate.worktree_root_path().to_path_buf()), connection: None, - request_args, + request_args: StartDebuggingRequestArguments { + request: self.request_kind(&config.config)?, + configuration, + }, }) } } diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 40d7147b63..7b80c303b5 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -462,6 +462,13 @@ impl DebugAdapter for GoDebugAdapter { ] }; + let mut configuration = task_definition.config.clone(); + if let Some(configuration) = configuration.as_object_mut() { + configuration + .entry("cwd") + .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + } + Ok(DebugAdapterBinary { command: Some(minidelve_path.to_string_lossy().into_owned()), arguments, @@ -469,7 +476,7 @@ impl DebugAdapter for GoDebugAdapter { envs: HashMap::default(), connection: None, request_args: StartDebuggingRequestArguments { - configuration: task_definition.config.clone(), + configuration, request: self.request_kind(&task_definition.config)?, }, }) diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 1f5422d4ae..67529f240a 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -2,6 +2,7 @@ use adapters::latest_github_release; use anyhow::Context as _; use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; use gpui::AsyncApp; +use serde_json::Value; use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use task::DebugRequest; use util::ResultExt; @@ -68,6 +69,15 @@ impl JsDebugAdapter { let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; + let mut configuration = task_definition.config.clone(); + if let Some(configuration) = configuration.as_object_mut() { + configuration + .entry("cwd") + .or_insert(delegate.worktree_root_path().to_string_lossy().into()); + + configuration.entry("type").and_modify(normalize_task_type); + } + Ok(DebugAdapterBinary { command: Some( delegate @@ -93,7 +103,7 @@ impl JsDebugAdapter { timeout, }), request_args: StartDebuggingRequestArguments { - configuration: task_definition.config.clone(), + configuration, request: self.request_kind(&task_definition.config)?, }, }) @@ -173,7 +183,7 @@ impl DebugAdapter for JsDebugAdapter { "properties": { "type": { "type": "string", - "enum": ["pwa-node", "node", "chrome", "pwa-chrome", "edge", "pwa-edge"], + "enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge"], "description": "The type of debug session", "default": "pwa-node" }, @@ -439,3 +449,19 @@ impl DebugAdapter for JsDebugAdapter { Some(label.to_owned()) } } + +fn normalize_task_type(task_type: &mut Value) { + let Some(task_type_str) = task_type.as_str() else { + return; + }; + + let new_name = match task_type_str { + "node" | "pwa-node" => "pwa-node", + "chrome" | "pwa-chrome" => "pwa-chrome", + "edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge", + _ => task_type_str, + } + .to_owned(); + + *task_type = Value::String(new_name); +} diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 886273d73f..2c65b8868e 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -71,6 +71,12 @@ impl PhpDebugAdapter { let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; + let mut configuration = task_definition.config.clone(); + if let Some(obj) = configuration.as_object_mut() { + obj.entry("cwd") + .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + } + Ok(DebugAdapterBinary { command: Some( delegate @@ -95,7 +101,7 @@ impl PhpDebugAdapter { cwd: Some(delegate.worktree_root_path().to_path_buf()), envs: HashMap::default(), request_args: StartDebuggingRequestArguments { - configuration: task_definition.config.clone(), + configuration, request: ::request_kind(self, &task_definition.config)?, }, }) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index a055bca21e..113c183cff 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -83,6 +83,7 @@ impl PythonDebugAdapter { fn request_args( &self, + delegate: &Arc, task_definition: &DebugTaskDefinition, ) -> Result { let request = self.request_kind(&task_definition.config)?; @@ -95,6 +96,11 @@ impl PythonDebugAdapter { } } + if let Some(obj) = configuration.as_object_mut() { + obj.entry("cwd") + .or_insert(delegate.worktree_root_path().to_string_lossy().into()); + } + Ok(StartDebuggingRequestArguments { configuration, request, @@ -196,7 +202,7 @@ impl PythonDebugAdapter { }), cwd: Some(delegate.worktree_root_path().to_path_buf()), envs: HashMap::default(), - request_args: self.request_args(config)?, + request_args: self.request_args(delegate, config)?, }) } } diff --git a/crates/dap_adapters/src/ruby.rs b/crates/dap_adapters/src/ruby.rs index 7c087e1702..47ed975909 100644 --- a/crates/dap_adapters/src/ruby.rs +++ b/crates/dap_adapters/src/ruby.rs @@ -174,6 +174,13 @@ impl DebugAdapter for RubyDebugAdapter { arguments.extend(ruby_config.args); + let mut configuration = definition.config.clone(); + if let Some(configuration) = configuration.as_object_mut() { + configuration + .entry("cwd") + .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + } + Ok(DebugAdapterBinary { command: Some(rdbg_path.to_string_lossy().to_string()), arguments, @@ -190,7 +197,7 @@ impl DebugAdapter for RubyDebugAdapter { envs: ruby_config.env.into_iter().collect(), request_args: StartDebuggingRequestArguments { request: self.request_kind(&definition.config)?, - configuration: definition.config.clone(), + configuration, }, }) } diff --git a/crates/task/src/vscode_debug_format.rs b/crates/task/src/vscode_debug_format.rs index 6ff617805c..9426f87cb7 100644 --- a/crates/task/src/vscode_debug_format.rs +++ b/crates/task/src/vscode_debug_format.rs @@ -1,5 +1,4 @@ use collections::HashMap; -use gpui::SharedString; use serde::Deserialize; use util::ResultExt as _; @@ -20,40 +19,32 @@ enum Request { struct VsCodeDebugTaskDefinition { r#type: String, name: String, - request: Request, - - #[serde(default)] - program: Option, - #[serde(default)] - args: Vec, - #[serde(default)] - env: HashMap>, - // TODO envFile? - #[serde(default)] - cwd: Option, #[serde(default)] port: Option, - #[serde(default)] - stop_on_entry: Option, #[serde(flatten)] other_attributes: serde_json::Value, } impl VsCodeDebugTaskDefinition { fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result { - let label = replacer.replace(&self.name).into(); - // TODO based on grep.app results it seems that vscode supports whitespace-splitting this field (ugh) + let label = replacer.replace(&self.name); + let mut config = replacer.replace_value(self.other_attributes); + let adapter = task_type_to_adapter_name(&self.r#type); + if let Some(config) = config.as_object_mut() { + if adapter == "JavaScript" { + config.insert("type".to_owned(), self.r#type.clone().into()); + } + } let definition = DebugScenario { - label, + label: label.into(), build: None, - adapter: task_type_to_adapter_name(&self.r#type), - // TODO host? + adapter: adapter.into(), tcp_connection: self.port.map(|port| TcpArgumentsTemplate { port: Some(port), host: None, timeout: None, }), - config: replacer.replace_value(self.other_attributes), + config, }; Ok(definition) } @@ -62,7 +53,8 @@ impl VsCodeDebugTaskDefinition { #[derive(Clone, Debug, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct VsCodeDebugTaskFile { - version: String, + #[serde(default)] + version: Option, configurations: Vec, } @@ -75,7 +67,11 @@ impl TryFrom for DebugTaskFile { "workspaceFolder".to_owned(), VariableName::WorktreeRoot.to_string(), ), - ("file".to_owned(), VariableName::Filename.to_string()), // TODO other interesting variables? + ( + "relativeFile".to_owned(), + VariableName::RelativeFile.to_string(), + ), + ("file".to_owned(), VariableName::File.to_string()), ])); let templates = file .configurations @@ -86,10 +82,10 @@ impl TryFrom for DebugTaskFile { } } -// todo(debugger) figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here -fn task_type_to_adapter_name(task_type: &str) -> SharedString { +fn task_type_to_adapter_name(task_type: &str) -> String { match task_type { - "node" => "JavaScript", + "pwa-node" | "node" | "chrome" | "pwa-chrome" | "edge" | "pwa-edge" | "msedge" + | "pwa-msedge" => "JavaScript", "go" => "Delve", "php" => "PHP", "cppdbg" | "lldb" => "CodeLLDB", @@ -98,7 +94,6 @@ fn task_type_to_adapter_name(task_type: &str) -> SharedString { _ => task_type, } .to_owned() - .into() } #[cfg(test)] @@ -141,7 +136,19 @@ mod tests { label: "Debug my JS app".into(), adapter: "JavaScript".into(), config: json!({ + "request": "launch", + "program": "${ZED_WORKTREE_ROOT}/xyz.js", "showDevDebugOutput": false, + "stopOnEntry": true, + "args": [ + "--foo", + "${ZED_WORKTREE_ROOT}/thing", + ], + "cwd": "${ZED_WORKTREE_ROOT}/${FOO}/sub", + "env": { + "X": "Y", + }, + "type": "node", }), tcp_connection: Some(TcpArgumentsTemplate { port: Some(17),