From ae0d4f6a0d58397355896fde3b01b0e3d6a50fd3 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:05:58 -0400 Subject: [PATCH] debugger: Add data breakpoint access type support (#34639) Release Notes: - Support specifying a data breakpoint's access type - Read, Write, Read & Write --- Cargo.lock | 1 + crates/debugger_ui/Cargo.toml | 13 ++-- crates/debugger_ui/src/debugger_ui.rs | 20 +++++- .../src/session/running/memory_view.rs | 2 +- .../src/session/running/variable_list.rs | 61 ++++++++++++++++--- 5 files changed, 77 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea7bfa0a38..a8a8d12e37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4427,6 +4427,7 @@ dependencies = [ "pretty_assertions", "project", "rpc", + "schemars", "serde", "serde_json", "serde_json_lenient", diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index ebb135c1d9..df4125860f 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -35,6 +35,7 @@ command_palette_hooks.workspace = true dap.workspace = true dap_adapters = { workspace = true, optional = true } db.workspace = true +debugger_tools.workspace = true editor.workspace = true file_icons.workspace = true futures.workspace = true @@ -54,6 +55,7 @@ picker.workspace = true pretty_assertions.workspace = true project.workspace = true rpc.workspace = true +schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true @@ -66,14 +68,13 @@ telemetry.workspace = true terminal_view.workspace = true text.workspace = true theme.workspace = true -tree-sitter.workspace = true tree-sitter-json.workspace = true +tree-sitter.workspace = true ui.workspace = true -util.workspace = true -workspace.workspace = true -workspace-hack.workspace = true -debugger_tools.workspace = true unindent = { workspace = true, optional = true } +util.workspace = true +workspace-hack.workspace = true +workspace.workspace = true zed_actions.workspace = true [dev-dependencies] @@ -83,8 +84,8 @@ debugger_tools = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] } +tree-sitter-go.workspace = true unindent.workspace = true util = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] } zlog.workspace = true -tree-sitter-go.workspace = true diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index c932f1b600..9eac59af83 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -3,10 +3,12 @@ use std::any::TypeId; use dap::debugger_settings::DebuggerSettings; use debugger_panel::DebugPanel; use editor::Editor; -use gpui::{App, DispatchPhase, EntityInputHandler, actions}; +use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions}; use new_process_modal::{NewProcessModal, NewProcessMode}; use onboarding_modal::DebuggerOnboardingModal; use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus}; +use schemars::JsonSchema; +use serde::Deserialize; use session::DebugSession; use settings::Settings; use stack_trace_view::StackTraceView; @@ -83,11 +85,23 @@ actions!( Rerun, /// Toggles expansion of the selected item in the debugger UI. ToggleExpandItem, - /// Set a data breakpoint on the selected variable or memory region. - ToggleDataBreakpoint, ] ); +/// Extends selection down by a specified number of lines. +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = debugger)] +#[serde(deny_unknown_fields)] +/// Set a data breakpoint on the selected variable or memory region. +pub struct ToggleDataBreakpoint { + /// The type of data breakpoint + /// Read & Write + /// Read + /// Write + #[serde(default)] + pub access_type: Option, +} + actions!( dev, [ diff --git a/crates/debugger_ui/src/session/running/memory_view.rs b/crates/debugger_ui/src/session/running/memory_view.rs index 499091ca0f..7b62a1d55d 100644 --- a/crates/debugger_ui/src/session/running/memory_view.rs +++ b/crates/debugger_ui/src/session/running/memory_view.rs @@ -688,7 +688,7 @@ impl MemoryView { menu = menu.action_disabled_when( *memory_unreadable, "Set Data Breakpoint", - ToggleDataBreakpoint.boxed_clone(), + ToggleDataBreakpoint { access_type: None }.boxed_clone(), ); } menu.context(self.focus_handle.clone()) diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index b158314b50..906e482687 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -670,9 +670,9 @@ impl VariableList { let focus_handle = self.focus_handle.clone(); cx.spawn_in(window, async move |this, cx| { let can_toggle_data_breakpoint = if let Some(task) = can_toggle_data_breakpoint { - task.await.is_some() + task.await } else { - true + None }; cx.update(|window, cx| { let context_menu = ContextMenu::build(window, cx, |menu, _, _| { @@ -686,11 +686,35 @@ impl VariableList { menu.action("Go To Memory", GoToMemory.boxed_clone()) }) .action("Watch Variable", AddWatch.boxed_clone()) - .when(can_toggle_data_breakpoint, |menu| { - menu.action( - "Toggle Data Breakpoint", - crate::ToggleDataBreakpoint.boxed_clone(), - ) + .when_some(can_toggle_data_breakpoint, |mut menu, data_info| { + menu = menu.separator(); + if let Some(access_types) = data_info.access_types { + for access in access_types { + menu = menu.action( + format!( + "Toggle {} Data Breakpoint", + match access { + dap::DataBreakpointAccessType::Read => "Read", + dap::DataBreakpointAccessType::Write => "Write", + dap::DataBreakpointAccessType::ReadWrite => + "Read/Write", + } + ), + crate::ToggleDataBreakpoint { + access_type: Some(access), + } + .boxed_clone(), + ); + } + + menu + } else { + menu.action( + "Toggle Data Breakpoint", + crate::ToggleDataBreakpoint { access_type: None } + .boxed_clone(), + ) + } }) }) .when(entry.as_watcher().is_some(), |menu| { @@ -729,7 +753,7 @@ impl VariableList { fn toggle_data_breakpoint( &mut self, - _: &crate::ToggleDataBreakpoint, + data_info: &crate::ToggleDataBreakpoint, _window: &mut Window, cx: &mut Context, ) { @@ -759,17 +783,34 @@ impl VariableList { }); let session = self.session.downgrade(); + let access_type = data_info.access_type; cx.spawn(async move |_, cx| { - let Some(data_id) = data_breakpoint.await.and_then(|info| info.data_id) else { + let Some((data_id, access_types)) = data_breakpoint + .await + .and_then(|info| Some((info.data_id?, info.access_types))) + else { return; }; + + // Because user's can manually add this action to the keymap + // we check if access type is supported + let access_type = match access_types { + None => None, + Some(access_types) => { + if access_type.is_some_and(|access_type| access_types.contains(&access_type)) { + access_type + } else { + None + } + } + }; _ = session.update(cx, |session, cx| { session.create_data_breakpoint( context, data_id.clone(), dap::DataBreakpoint { data_id, - access_type: None, + access_type, condition: None, hit_condition: None, },