debugger: A support for data breakpoint's on variables (#34391)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Anthony Eid 2025-07-14 13:45:46 -04:00 committed by GitHub
parent 8b6b039b63
commit fd5650d4ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 588 additions and 87 deletions

View file

@ -1,4 +1,10 @@
use std::{fmt::Write, ops::RangeInclusive, sync::LazyLock, time::Duration};
use std::{
cell::LazyCell,
fmt::Write,
ops::RangeInclusive,
sync::{Arc, LazyLock},
time::Duration,
};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
@ -8,7 +14,7 @@ use gpui::{
deferred, point, size, uniform_list,
};
use notifications::status_toast::{StatusToast, ToastIcon};
use project::debugger::{MemoryCell, session::Session};
use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};
use settings::Settings;
use theme::ThemeSettings;
use ui::{
@ -20,7 +26,7 @@ use ui::{
use util::ResultExt;
use workspace::Workspace;
use crate::session::running::stack_frame_list::StackFrameList;
use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
actions!(debugger, [GoToSelectedAddress]);
@ -446,6 +452,48 @@ impl MemoryView {
}
}
fn toggle_data_breakpoint(
&mut self,
_: &crate::ToggleDataBreakpoint,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some(SelectedMemoryRange::DragComplete(selection)) = self.view_state.selection.clone()
else {
return;
};
let range = selection.memory_range();
let context = Arc::new(DataBreakpointContext::Address {
address: range.start().to_string(),
bytes: Some(*range.end() - *range.start()),
});
self.session.update(cx, |this, cx| {
let data_breakpoint_info = this.data_breakpoint_info(context.clone(), None, cx);
cx.spawn(async move |this, cx| {
if let Some(info) = data_breakpoint_info.await {
let Some(data_id) = info.data_id.clone() else {
return;
};
_ = this.update(cx, |this, cx| {
this.create_data_breakpoint(
context,
data_id.clone(),
dap::DataBreakpoint {
data_id,
access_type: None,
condition: None,
hit_condition: None,
},
cx,
);
});
}
})
.detach();
})
}
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(SelectedMemoryRange::DragComplete(drag)) = &self.view_state.selection {
// Go into memory writing mode.
@ -599,18 +647,30 @@ impl MemoryView {
let session = self.session.clone();
let context_menu = ContextMenu::build(window, cx, |menu, _, cx| {
let range_too_large = range.end() - range.start() > std::mem::size_of::<u64>() as u64;
let memory_unreadable = |cx| {
let caps = session.read(cx).capabilities();
let supports_data_breakpoints = caps.supports_data_breakpoints.unwrap_or_default()
&& caps.supports_data_breakpoint_bytes.unwrap_or_default();
let memory_unreadable = LazyCell::new(|| {
session.update(cx, |this, cx| {
this.read_memory(range.clone(), cx)
.any(|cell| cell.0.is_none())
})
};
menu.action_disabled_when(
range_too_large || memory_unreadable(cx),
});
let mut menu = menu.action_disabled_when(
range_too_large || *memory_unreadable,
"Go To Selected Address",
GoToSelectedAddress.boxed_clone(),
)
.context(self.focus_handle.clone())
);
if supports_data_breakpoints {
menu = menu.action_disabled_when(
*memory_unreadable,
"Set Data Breakpoint",
ToggleDataBreakpoint.boxed_clone(),
);
}
menu.context(self.focus_handle.clone())
});
cx.focus_view(&context_menu, window);
@ -834,6 +894,7 @@ impl Render for MemoryView {
.on_action(cx.listener(Self::go_to_address))
.p_1()
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::toggle_data_breakpoint))
.on_action(cx.listener(Self::page_down))
.on_action(cx.listener(Self::page_up))
.size_full()