debugger: Add data breakpoint access type support (#34639)

Release Notes:

- Support specifying a data breakpoint's access type - Read, Write, Read
& Write
This commit is contained in:
Anthony Eid 2025-07-17 13:05:58 -04:00 committed by GitHub
parent 8980526a85
commit ae0d4f6a0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 20 deletions

1
Cargo.lock generated
View file

@ -4427,6 +4427,7 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"project", "project",
"rpc", "rpc",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_json_lenient", "serde_json_lenient",

View file

@ -35,6 +35,7 @@ command_palette_hooks.workspace = true
dap.workspace = true dap.workspace = true
dap_adapters = { workspace = true, optional = true } dap_adapters = { workspace = true, optional = true }
db.workspace = true db.workspace = true
debugger_tools.workspace = true
editor.workspace = true editor.workspace = true
file_icons.workspace = true file_icons.workspace = true
futures.workspace = true futures.workspace = true
@ -54,6 +55,7 @@ picker.workspace = true
pretty_assertions.workspace = true pretty_assertions.workspace = true
project.workspace = true project.workspace = true
rpc.workspace = true rpc.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde_json_lenient.workspace = true serde_json_lenient.workspace = true
@ -66,14 +68,13 @@ telemetry.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
text.workspace = true text.workspace = true
theme.workspace = true theme.workspace = true
tree-sitter.workspace = true
tree-sitter-json.workspace = true tree-sitter-json.workspace = true
tree-sitter.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
debugger_tools.workspace = true
unindent = { workspace = true, optional = true } unindent = { workspace = true, optional = true }
util.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true
[dev-dependencies] [dev-dependencies]
@ -83,8 +84,8 @@ debugger_tools = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
tree-sitter-go.workspace = true
unindent.workspace = true unindent.workspace = true
util = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] }
zlog.workspace = true zlog.workspace = true
tree-sitter-go.workspace = true

View file

@ -3,10 +3,12 @@ use std::any::TypeId;
use dap::debugger_settings::DebuggerSettings; use dap::debugger_settings::DebuggerSettings;
use debugger_panel::DebugPanel; use debugger_panel::DebugPanel;
use editor::Editor; use editor::Editor;
use gpui::{App, DispatchPhase, EntityInputHandler, actions}; use gpui::{Action, App, DispatchPhase, EntityInputHandler, actions};
use new_process_modal::{NewProcessModal, NewProcessMode}; use new_process_modal::{NewProcessModal, NewProcessMode};
use onboarding_modal::DebuggerOnboardingModal; use onboarding_modal::DebuggerOnboardingModal;
use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus}; use project::debugger::{self, breakpoint_store::SourceBreakpoint, session::ThreadStatus};
use schemars::JsonSchema;
use serde::Deserialize;
use session::DebugSession; use session::DebugSession;
use settings::Settings; use settings::Settings;
use stack_trace_view::StackTraceView; use stack_trace_view::StackTraceView;
@ -83,11 +85,23 @@ actions!(
Rerun, Rerun,
/// Toggles expansion of the selected item in the debugger UI. /// Toggles expansion of the selected item in the debugger UI.
ToggleExpandItem, 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<dap::DataBreakpointAccessType>,
}
actions!( actions!(
dev, dev,
[ [

View file

@ -688,7 +688,7 @@ impl MemoryView {
menu = menu.action_disabled_when( menu = menu.action_disabled_when(
*memory_unreadable, *memory_unreadable,
"Set Data Breakpoint", "Set Data Breakpoint",
ToggleDataBreakpoint.boxed_clone(), ToggleDataBreakpoint { access_type: None }.boxed_clone(),
); );
} }
menu.context(self.focus_handle.clone()) menu.context(self.focus_handle.clone())

View file

@ -670,9 +670,9 @@ impl VariableList {
let focus_handle = self.focus_handle.clone(); let focus_handle = self.focus_handle.clone();
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
let can_toggle_data_breakpoint = if let Some(task) = can_toggle_data_breakpoint { let can_toggle_data_breakpoint = if let Some(task) = can_toggle_data_breakpoint {
task.await.is_some() task.await
} else { } else {
true None
}; };
cx.update(|window, cx| { cx.update(|window, cx| {
let context_menu = ContextMenu::build(window, cx, |menu, _, _| { let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
@ -686,11 +686,35 @@ impl VariableList {
menu.action("Go To Memory", GoToMemory.boxed_clone()) menu.action("Go To Memory", GoToMemory.boxed_clone())
}) })
.action("Watch Variable", AddWatch.boxed_clone()) .action("Watch Variable", AddWatch.boxed_clone())
.when(can_toggle_data_breakpoint, |menu| { .when_some(can_toggle_data_breakpoint, |mut menu, data_info| {
menu.action( menu = menu.separator();
"Toggle Data Breakpoint", if let Some(access_types) = data_info.access_types {
crate::ToggleDataBreakpoint.boxed_clone(), 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| { .when(entry.as_watcher().is_some(), |menu| {
@ -729,7 +753,7 @@ impl VariableList {
fn toggle_data_breakpoint( fn toggle_data_breakpoint(
&mut self, &mut self,
_: &crate::ToggleDataBreakpoint, data_info: &crate::ToggleDataBreakpoint,
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
@ -759,17 +783,34 @@ impl VariableList {
}); });
let session = self.session.downgrade(); let session = self.session.downgrade();
let access_type = data_info.access_type;
cx.spawn(async move |_, cx| { 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; 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.update(cx, |session, cx| {
session.create_data_breakpoint( session.create_data_breakpoint(
context, context,
data_id.clone(), data_id.clone(),
dap::DataBreakpoint { dap::DataBreakpoint {
data_id, data_id,
access_type: None, access_type,
condition: None, condition: None,
hit_condition: None, hit_condition: None,
}, },