From 804de3316ed3240b55b9927e38ddd6de92d01c17 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Fri, 30 May 2025 04:22:16 +0300 Subject: [PATCH] debugger: Update docs with more examples (#31597) This PR also shows more completion items when defining a debug config in a `debug.json` file. Mainly when using a pre build task argument. ### Follow ups - Add docs for Go, JS, PHP - Add attach docs Release Notes: - debugger beta: Show build task completions when editing a debug.json configuration with a pre build task - debugger beta: Add Python and Native Code debug config [examples](https://zed.dev/docs/debugger) --- Cargo.lock | 1 + crates/dap_adapters/src/python.rs | 2 +- crates/task/Cargo.toml | 3 +- crates/task/src/debug_format.rs | 203 +++++++++++++++++++++++++++++- docs/src/debugger.md | 123 +++++++++++++++--- 5 files changed, 310 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b190fcbf7b..f2b80c980e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15588,6 +15588,7 @@ dependencies = [ "futures 0.3.31", "gpui", "hex", + "log", "parking_lot", "pretty_assertions", "proto", diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 5213829f4f..a00736f4a7 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -660,7 +660,7 @@ impl DebugAdapter for PythonDebugAdapter { } } - self.get_installed_binary(delegate, &config, None, None, false) + self.get_installed_binary(delegate, &config, None, toolchain, false) .await } } diff --git a/crates/task/Cargo.toml b/crates/task/Cargo.toml index ab07524e08..f79b39616f 100644 --- a/crates/task/Cargo.toml +++ b/crates/task/Cargo.toml @@ -20,6 +20,7 @@ collections.workspace = true futures.workspace = true gpui.workspace = true hex.workspace = true +log.workspace = true parking_lot.workspace = true proto.workspace = true schemars.workspace = true @@ -29,8 +30,8 @@ serde_json_lenient.workspace = true sha2.workspace = true shellexpand.workspace = true util.workspace = true -zed_actions.workspace = true workspace-hack.workspace = true +zed_actions.workspace = true [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index 293b77e6e5..0d9733ebff 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -1,10 +1,12 @@ use anyhow::{Context as _, Result}; use collections::FxHashMap; use gpui::SharedString; +use log as _; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::net::Ipv4Addr; use std::path::PathBuf; +use util::debug_panic; use crate::{TaskTemplate, adapter_schema::AdapterSchemas}; @@ -182,7 +184,7 @@ impl From for DebugRequest { } } -#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] +#[derive(Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] #[serde(untagged)] pub enum BuildTaskDefinition { ByName(SharedString), @@ -194,6 +196,47 @@ pub enum BuildTaskDefinition { }, } +impl<'de> Deserialize<'de> for BuildTaskDefinition { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct TemplateHelper { + #[serde(default)] + label: Option, + #[serde(flatten)] + rest: serde_json::Value, + } + + let value = serde_json::Value::deserialize(deserializer)?; + + if let Ok(name) = serde_json::from_value::(value.clone()) { + return Ok(BuildTaskDefinition::ByName(name)); + } + + let helper: TemplateHelper = + serde_json::from_value(value).map_err(serde::de::Error::custom)?; + + let mut template_value = helper.rest; + if let serde_json::Value::Object(ref mut map) = template_value { + map.insert( + "label".to_string(), + serde_json::to_value(helper.label.unwrap_or_else(|| "debug-build".to_owned())) + .map_err(serde::de::Error::custom)?, + ); + } + + let task_template: TaskTemplate = + serde_json::from_value(template_value).map_err(serde::de::Error::custom)?; + + Ok(BuildTaskDefinition::Template { + task_template, + locator_name: None, + }) + } +} + #[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, JsonSchema)] pub enum Request { Launch, @@ -243,9 +286,96 @@ pub struct DebugScenario { pub struct DebugTaskFile(pub Vec); impl DebugTaskFile { - /// Generates JSON schema of Tasks JSON template format. pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value { - schemas.generate_json_schema().unwrap_or_default() + let build_task_schema = schemars::schema_for!(BuildTaskDefinition); + let mut build_task_value = + serde_json_lenient::to_value(&build_task_schema).unwrap_or_default(); + + if let Some(template_object) = build_task_value + .get_mut("anyOf") + .and_then(|array| array.as_array_mut()) + .and_then(|array| array.get_mut(1)) + { + if let Some(properties) = template_object + .get_mut("properties") + .and_then(|value| value.as_object_mut()) + { + properties.remove("label"); + } + + if let Some(arr) = template_object + .get_mut("required") + .and_then(|array| array.as_array_mut()) + { + arr.retain(|v| v.as_str() != Some("label")); + } + } else { + debug_panic!("Task Template schema in debug scenario's needs to be updated"); + } + + let task_definitions = build_task_value + .get("definitions") + .cloned() + .unwrap_or_default(); + + let adapter_conditions = schemas + .0 + .iter() + .map(|adapter_schema| { + let adapter_name = adapter_schema.adapter.to_string(); + serde_json::json!({ + "if": { + "properties": { + "adapter": { "const": adapter_name } + } + }, + "then": adapter_schema.schema + }) + }) + .collect::>(); + + serde_json_lenient::json!({ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Debug Configurations", + "description": "Configuration for debug scenarios", + "type": "array", + "items": { + "type": "object", + "required": ["adapter", "label"], + "properties": { + "adapter": { + "type": "string", + "description": "The name of the debug adapter" + }, + "label": { + "type": "string", + "description": "The name of the debug configuration" + }, + "build": build_task_value, + "tcp_connection": { + "type": "object", + "description": "Optional TCP connection information for connecting to an already running debug adapter", + "properties": { + "port": { + "type": "integer", + "description": "The port that the debug adapter is listening on (default: auto-find open port)" + }, + "host": { + "type": "string", + "pattern": "^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$", + "description": "The host that the debug adapter is listening to (default: 127.0.0.1)" + }, + "timeout": { + "type": "integer", + "description": "The max amount of time in milliseconds to connect to a tcp DAP before returning an error (default: 2000ms)" + } + } + } + }, + "allOf": adapter_conditions + }, + "definitions": task_definitions + }) } } @@ -254,6 +384,32 @@ mod tests { use crate::DebugScenario; use serde_json::json; + #[test] + fn test_just_build_args() { + let json = r#"{ + "label": "Build & debug rust", + "adapter": "CodeLLDB", + "build": { + "command": "rust", + "args": ["build"] + } + }"#; + + let deserialized: DebugScenario = serde_json::from_str(json).unwrap(); + assert!(deserialized.build.is_some()); + match deserialized.build.as_ref().unwrap() { + crate::BuildTaskDefinition::Template { task_template, .. } => { + assert_eq!("debug-build", task_template.label); + assert_eq!("rust", task_template.command); + assert_eq!(vec!["build"], task_template.args); + } + _ => panic!("Expected Template variant"), + } + assert_eq!(json!({}), deserialized.config); + assert_eq!("CodeLLDB", deserialized.adapter.as_ref()); + assert_eq!("Build & debug rust", deserialized.label.as_ref()); + } + #[test] fn test_empty_scenario_has_none_request() { let json = r#"{ @@ -307,4 +463,45 @@ mod tests { assert_eq!("CodeLLDB", deserialized.adapter.as_ref()); assert_eq!("Attach to process", deserialized.label.as_ref()); } + + #[test] + fn test_build_task_definition_without_label() { + use crate::BuildTaskDefinition; + + let json = r#""my_build_task""#; + let deserialized: BuildTaskDefinition = serde_json::from_str(json).unwrap(); + match deserialized { + BuildTaskDefinition::ByName(name) => assert_eq!("my_build_task", name.as_ref()), + _ => panic!("Expected ByName variant"), + } + + let json = r#"{ + "command": "cargo", + "args": ["build", "--release"] + }"#; + let deserialized: BuildTaskDefinition = serde_json::from_str(json).unwrap(); + match deserialized { + BuildTaskDefinition::Template { task_template, .. } => { + assert_eq!("debug-build", task_template.label); + assert_eq!("cargo", task_template.command); + assert_eq!(vec!["build", "--release"], task_template.args); + } + _ => panic!("Expected Template variant"), + } + + let json = r#"{ + "label": "Build Release", + "command": "cargo", + "args": ["build", "--release"] + }"#; + let deserialized: BuildTaskDefinition = serde_json::from_str(json).unwrap(); + match deserialized { + BuildTaskDefinition::Template { task_template, .. } => { + assert_eq!("Build Release", task_template.label); + assert_eq!("cargo", task_template.command); + assert_eq!(vec!["build", "--release"], task_template.args); + } + _ => panic!("Expected Template variant"), + } + } } diff --git a/docs/src/debugger.md b/docs/src/debugger.md index ccfc6dd00f..d23de9542c 100644 --- a/docs/src/debugger.md +++ b/docs/src/debugger.md @@ -28,15 +28,15 @@ These adapters enable Zed to provide a consistent debugging experience across mu ## Getting Started -For basic debugging you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or clicking the plus icon at the top right of the debug panel. +For basic debugging, you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or by clicking the plus icon at the top right of the debug panel. -For more advanced use cases you can create debug configurations by directly editing the `.zed/debug.json` file in your project root directory. +For more advanced use cases, you can create debug configurations by directly editing the `.zed/debug.json` file in your project root directory. -You can then use the `New Session Modal` to select a configuration then start debugging. +You can then use the `New Session Modal` to select a configuration and start debugging. ### Configuration -While configuration fields are debug adapter dependent, most adapters support the following fields. +While configuration fields are debug adapter-dependent, most adapters support the following fields: ```json [ @@ -58,22 +58,114 @@ While configuration fields are debug adapter dependent, most adapters support th ] ``` -#### Task Variables +#### Tasks -All configuration fields support task variables. See [Tasks](./tasks.md) +All configuration fields support task variables. See [Tasks Variables](./tasks.md#variables) + +Zed also allows embedding a task that is run before the debugger starts. This is useful for setting up the environment or running any necessary setup steps before the debugger starts. + +See an example [here](#build-binary-then-debug) + +#### Python Examples + +##### Python Active File + +```json +[ + { + "label": "Active File", + "adapter": "Debugpy", + "program": "$ZED_FILE", + "request": "launch" + } +] +``` + +##### Flask App + +For a common Flask Application with a file structure similar to the following: + +- .venv/ +- app/ + - **init**.py + - **main**.py + - routes.py +- templates/ + - index.html +- static/ + - style.css +- requirements.txt + +```json +[ + { + "label": "Python: Flask", + "adapter": "Debugpy", + "request": "launch", + "module": "app", + "cwd": "$ZED_WORKTREE_ROOT", + "env": { + "FLASK_APP": "app", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--reload", // Enables Flask reloader that watches for file changes + "--debugger" // Enables Flask debugger + ], + "autoReload": { + "enable": true + }, + "jinja": true, + "justMyCode": true + } +] +``` + +#### Rust/C++/C + +##### Using pre-built binary + +```json +[ + { + "label": "Debug native binary", + "program": "$ZED_WORKTREE_ROOT/build/binary", + "request": "launch", + "adapter": "CodeLLDB" // GDB is available on non arm macs as well as linux + } +] +``` + +##### Build binary then debug + +```json +[ + { + "label": "Build & Debug native binary", + "build": { + "command": "cargo", + "args": ["build"] + }, + "program": "$ZED_WORKTREE_ROOT/target/debug/binary", + "request": "launch", + "adapter": "CodeLLDB" // GDB is available on non arm macs as well as linux + } +] +``` ## Breakpoints -Zed currently supports these types of breakpoints +Zed currently supports these types of breakpoints: - Standard Breakpoints: Stop at the breakpoint when it's hit - Log Breakpoints: Output a log message instead of stopping at the breakpoint when it's hit - Conditional Breakpoints: Stop at the breakpoint when it's hit if the condition is met - Hit Breakpoints: Stop at the breakpoint when it's hit a certain number of times -Standard breakpoints can be toggled by left clicking on the editor gutter or using the Toggle Breakpoint action. Right clicking on a breakpoint or on a code runner symbol brings up the breakpoint context menu. This has options for toggling breakpoints and editing log breakpoints. +Standard breakpoints can be toggled by left-clicking on the editor gutter or using the Toggle Breakpoint action. Right-clicking on a breakpoint or on a code runner symbol brings up the breakpoint context menu. This has options for toggling breakpoints and editing log breakpoints. -Other kinds of breakpoints can be toggled/edited by right clicking on the breakpoint icon in the gutter and selecting the desired option. +Other kinds of breakpoints can be toggled/edited by right-clicking on the breakpoint icon in the gutter and selecting the desired option. ## Settings @@ -81,8 +173,8 @@ Other kinds of breakpoints can be toggled/edited by right clicking on the breakp - `save_breakpoints`: Whether the breakpoints should be reused across Zed sessions. - `button`: Whether to show the debug button in the status bar. - `timeout`: Time in milliseconds until timeout error when connecting to a TCP debug adapter. -- `log_dap_communications`: Whether to log messages between active debug adapters and Zed -- `format_dap_log_messages`: Whether to format dap messages in when adding them to debug adapter logger +- `log_dap_communications`: Whether to log messages between active debug adapters and Zed. +- `format_dap_log_messages`: Whether to format DAP messages when adding them to the debug adapter logger. ### Stepping granularity @@ -163,7 +255,7 @@ Other kinds of breakpoints can be toggled/edited by right clicking on the breakp ### Timeout - Description: Time in milliseconds until timeout error when connecting to a TCP debug adapter. -- Default: 2000ms +- Default: 2000 - Setting: debugger.timeout **Options** @@ -198,7 +290,7 @@ Other kinds of breakpoints can be toggled/edited by right clicking on the breakp ### Format Dap Log Messages -- Description: Whether to format dap messages in when adding them to debug adapter logger. (Used for DAP development) +- Description: Whether to format DAP messages when adding them to the debug adapter logger. (Used for DAP development) - Default: false - Setting: debugger.format_dap_log_messages @@ -218,8 +310,5 @@ Other kinds of breakpoints can be toggled/edited by right clicking on the breakp The Debugger supports the following theme options: - /// Color used to accent some of the debugger's elements - /// Only accents breakpoint & breakpoint related symbols right now - -**debugger.accent**: Color used to accent breakpoint & breakpoint related symbols +**debugger.accent**: Color used to accent breakpoint & breakpoint-related symbols **editor.debugger_active_line.background**: Background color of active debug line