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:
parent
8b6b039b63
commit
fd5650d4ed
6 changed files with 588 additions and 87 deletions
|
@ -83,6 +83,8 @@ 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,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,10 @@ use project::{
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div,
|
ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div,
|
||||||
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, Indicator,
|
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, InteractiveElement,
|
||||||
InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement,
|
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
||||||
Render, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
|
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Toggleable,
|
||||||
Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
|
Tooltip, Window, div, h_flex, px, v_flex,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
@ -46,6 +46,7 @@ actions!(
|
||||||
pub(crate) enum SelectedBreakpointKind {
|
pub(crate) enum SelectedBreakpointKind {
|
||||||
Source,
|
Source,
|
||||||
Exception,
|
Exception,
|
||||||
|
Data,
|
||||||
}
|
}
|
||||||
pub(crate) struct BreakpointList {
|
pub(crate) struct BreakpointList {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
@ -188,6 +189,9 @@ impl BreakpointList {
|
||||||
BreakpointEntryKind::ExceptionBreakpoint(bp) => {
|
BreakpointEntryKind::ExceptionBreakpoint(bp) => {
|
||||||
(SelectedBreakpointKind::Exception, bp.is_enabled)
|
(SelectedBreakpointKind::Exception, bp.is_enabled)
|
||||||
}
|
}
|
||||||
|
BreakpointEntryKind::DataBreakpoint(bp) => {
|
||||||
|
(SelectedBreakpointKind::Data, bp.0.is_enabled)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -391,7 +395,8 @@ impl BreakpointList {
|
||||||
let row = line_breakpoint.breakpoint.row;
|
let row = line_breakpoint.breakpoint.row;
|
||||||
self.go_to_line_breakpoint(path, row, window, cx);
|
self.go_to_line_breakpoint(path, row, window, cx);
|
||||||
}
|
}
|
||||||
BreakpointEntryKind::ExceptionBreakpoint(_) => {}
|
BreakpointEntryKind::DataBreakpoint(_)
|
||||||
|
| BreakpointEntryKind::ExceptionBreakpoint(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,6 +426,10 @@ impl BreakpointList {
|
||||||
let id = exception_breakpoint.id.clone();
|
let id = exception_breakpoint.id.clone();
|
||||||
self.toggle_exception_breakpoint(&id, cx);
|
self.toggle_exception_breakpoint(&id, cx);
|
||||||
}
|
}
|
||||||
|
BreakpointEntryKind::DataBreakpoint(data_breakpoint) => {
|
||||||
|
let id = data_breakpoint.0.dap.data_id.clone();
|
||||||
|
self.toggle_data_breakpoint(&id, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -441,7 +450,7 @@ impl BreakpointList {
|
||||||
let row = line_breakpoint.breakpoint.row;
|
let row = line_breakpoint.breakpoint.row;
|
||||||
self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx);
|
self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx);
|
||||||
}
|
}
|
||||||
BreakpointEntryKind::ExceptionBreakpoint(_) => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -490,6 +499,14 @@ impl BreakpointList {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {
|
||||||
|
if let Some(session) = &self.session {
|
||||||
|
session.update(cx, |this, cx| {
|
||||||
|
this.toggle_data_breakpoint(&id, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_exception_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {
|
fn toggle_exception_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {
|
||||||
if let Some(session) = &self.session {
|
if let Some(session) = &self.session {
|
||||||
session.update(cx, |this, cx| {
|
session.update(cx, |this, cx| {
|
||||||
|
@ -642,6 +659,7 @@ impl BreakpointList {
|
||||||
SelectedBreakpointKind::Exception => {
|
SelectedBreakpointKind::Exception => {
|
||||||
"Exception Breakpoints cannot be removed from the breakpoint list"
|
"Exception Breakpoints cannot be removed from the breakpoint list"
|
||||||
}
|
}
|
||||||
|
SelectedBreakpointKind::Data => "Remove data breakpoint from a breakpoint list",
|
||||||
});
|
});
|
||||||
let toggle_label = selection_kind.map(|(_, is_enabled)| {
|
let toggle_label = selection_kind.map(|(_, is_enabled)| {
|
||||||
if is_enabled {
|
if is_enabled {
|
||||||
|
@ -783,8 +801,20 @@ impl Render for BreakpointList {
|
||||||
weak: weak.clone(),
|
weak: weak.clone(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
self.breakpoints
|
let data_breakpoints = self.session.as_ref().into_iter().flat_map(|session| {
|
||||||
.extend(breakpoints.chain(exception_breakpoints));
|
session
|
||||||
|
.read(cx)
|
||||||
|
.data_breakpoints()
|
||||||
|
.map(|state| BreakpointEntry {
|
||||||
|
kind: BreakpointEntryKind::DataBreakpoint(DataBreakpoint(state.clone())),
|
||||||
|
weak: weak.clone(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
self.breakpoints.extend(
|
||||||
|
breakpoints
|
||||||
|
.chain(data_breakpoints)
|
||||||
|
.chain(exception_breakpoints),
|
||||||
|
);
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("breakpoint-list")
|
.id("breakpoint-list")
|
||||||
.key_context("BreakpointList")
|
.key_context("BreakpointList")
|
||||||
|
@ -905,7 +935,11 @@ impl LineBreakpoint {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(Indicator::icon(Icon::new(icon_name)).color(Color::Debugger))
|
.child(
|
||||||
|
Icon::new(icon_name)
|
||||||
|
.color(Color::Debugger)
|
||||||
|
.size(IconSize::XSmall),
|
||||||
|
)
|
||||||
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
.on_mouse_down(MouseButton::Left, move |_, _, _| {});
|
||||||
|
|
||||||
ListItem::new(SharedString::from(format!(
|
ListItem::new(SharedString::from(format!(
|
||||||
|
@ -996,6 +1030,103 @@ struct ExceptionBreakpoint {
|
||||||
data: ExceptionBreakpointsFilter,
|
data: ExceptionBreakpointsFilter,
|
||||||
is_enabled: bool,
|
is_enabled: bool,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct DataBreakpoint(project::debugger::session::DataBreakpointState);
|
||||||
|
|
||||||
|
impl DataBreakpoint {
|
||||||
|
fn render(
|
||||||
|
&self,
|
||||||
|
props: SupportedBreakpointProperties,
|
||||||
|
strip_mode: Option<ActiveBreakpointStripMode>,
|
||||||
|
ix: usize,
|
||||||
|
is_selected: bool,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
list: WeakEntity<BreakpointList>,
|
||||||
|
) -> ListItem {
|
||||||
|
let color = if self.0.is_enabled {
|
||||||
|
Color::Debugger
|
||||||
|
} else {
|
||||||
|
Color::Muted
|
||||||
|
};
|
||||||
|
let is_enabled = self.0.is_enabled;
|
||||||
|
let id = self.0.dap.data_id.clone();
|
||||||
|
ListItem::new(SharedString::from(format!(
|
||||||
|
"data-breakpoint-ui-item-{}",
|
||||||
|
self.0.dap.data_id
|
||||||
|
)))
|
||||||
|
.rounded()
|
||||||
|
.start_slot(
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!(
|
||||||
|
"data-breakpoint-ui-item-{}-click-handler",
|
||||||
|
self.0.dap.data_id
|
||||||
|
)))
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
if is_enabled {
|
||||||
|
"Disable Data Breakpoint"
|
||||||
|
} else {
|
||||||
|
"Enable Data Breakpoint"
|
||||||
|
},
|
||||||
|
&ToggleEnableBreakpoint,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click({
|
||||||
|
let list = list.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
list.update(cx, |this, cx| {
|
||||||
|
this.toggle_data_breakpoint(&id, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cursor_pointer()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::Binary)
|
||||||
|
.color(color)
|
||||||
|
.size(IconSize::Small),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.mr_4()
|
||||||
|
.py_0p5()
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.py_1()
|
||||||
|
.gap_1()
|
||||||
|
.min_h(px(26.))
|
||||||
|
.justify_center()
|
||||||
|
.id(("data-breakpoint-label", ix))
|
||||||
|
.child(
|
||||||
|
Label::new(self.0.context.human_readable_label())
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.line_height_style(ui::LineHeightStyle::UiLabel),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(BreakpointOptionsStrip {
|
||||||
|
props,
|
||||||
|
breakpoint: BreakpointEntry {
|
||||||
|
kind: BreakpointEntryKind::DataBreakpoint(self.clone()),
|
||||||
|
weak: list,
|
||||||
|
},
|
||||||
|
is_selected,
|
||||||
|
focus_handle,
|
||||||
|
strip_mode,
|
||||||
|
index: ix,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.toggle_state(is_selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ExceptionBreakpoint {
|
impl ExceptionBreakpoint {
|
||||||
fn render(
|
fn render(
|
||||||
|
@ -1062,7 +1193,11 @@ impl ExceptionBreakpoint {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
.child(
|
||||||
|
Icon::new(IconName::Flame)
|
||||||
|
.color(color)
|
||||||
|
.size(IconSize::Small),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -1105,6 +1240,7 @@ impl ExceptionBreakpoint {
|
||||||
enum BreakpointEntryKind {
|
enum BreakpointEntryKind {
|
||||||
LineBreakpoint(LineBreakpoint),
|
LineBreakpoint(LineBreakpoint),
|
||||||
ExceptionBreakpoint(ExceptionBreakpoint),
|
ExceptionBreakpoint(ExceptionBreakpoint),
|
||||||
|
DataBreakpoint(DataBreakpoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -1140,6 +1276,14 @@ impl BreakpointEntry {
|
||||||
focus_handle,
|
focus_handle,
|
||||||
self.weak.clone(),
|
self.weak.clone(),
|
||||||
),
|
),
|
||||||
|
BreakpointEntryKind::DataBreakpoint(data_breakpoint) => data_breakpoint.render(
|
||||||
|
props.for_data_breakpoints(),
|
||||||
|
strip_mode,
|
||||||
|
ix,
|
||||||
|
is_selected,
|
||||||
|
focus_handle,
|
||||||
|
self.weak.clone(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1155,6 +1299,11 @@ impl BreakpointEntry {
|
||||||
exception_breakpoint.id
|
exception_breakpoint.id
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
|
BreakpointEntryKind::DataBreakpoint(data_breakpoint) => format!(
|
||||||
|
"data-breakpoint-control-strip--{}",
|
||||||
|
data_breakpoint.0.dap.data_id
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1172,8 +1321,8 @@ impl BreakpointEntry {
|
||||||
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
|
||||||
line_breakpoint.breakpoint.condition.is_some()
|
line_breakpoint.breakpoint.condition.is_some()
|
||||||
}
|
}
|
||||||
// We don't support conditions on exception breakpoints
|
// We don't support conditions on exception/data breakpoints
|
||||||
BreakpointEntryKind::ExceptionBreakpoint(_) => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1225,6 +1374,10 @@ impl SupportedBreakpointProperties {
|
||||||
// TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here.
|
// TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here.
|
||||||
Self::empty()
|
Self::empty()
|
||||||
}
|
}
|
||||||
|
fn for_data_breakpoints(self) -> Self {
|
||||||
|
// TODO: we don't yet support conditions for data breakpoints at the data layer, hence all props are disabled here.
|
||||||
|
Self::empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
struct BreakpointOptionsStrip {
|
struct BreakpointOptionsStrip {
|
||||||
|
|
|
@ -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 editor::{Editor, EditorElement, EditorStyle};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -8,7 +14,7 @@ use gpui::{
|
||||||
deferred, point, size, uniform_list,
|
deferred, point, size, uniform_list,
|
||||||
};
|
};
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
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 settings::Settings;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -20,7 +26,7 @@ use ui::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::session::running::stack_frame_list::StackFrameList;
|
use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
|
||||||
|
|
||||||
actions!(debugger, [GoToSelectedAddress]);
|
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>) {
|
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let Some(SelectedMemoryRange::DragComplete(drag)) = &self.view_state.selection {
|
if let Some(SelectedMemoryRange::DragComplete(drag)) = &self.view_state.selection {
|
||||||
// Go into memory writing mode.
|
// Go into memory writing mode.
|
||||||
|
@ -599,18 +647,30 @@ impl MemoryView {
|
||||||
let session = self.session.clone();
|
let session = self.session.clone();
|
||||||
let context_menu = ContextMenu::build(window, cx, |menu, _, cx| {
|
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 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| {
|
session.update(cx, |this, cx| {
|
||||||
this.read_memory(range.clone(), cx)
|
this.read_memory(range.clone(), cx)
|
||||||
.any(|cell| cell.0.is_none())
|
.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",
|
"Go To Selected Address",
|
||||||
GoToSelectedAddress.boxed_clone(),
|
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);
|
cx.focus_view(&context_menu, window);
|
||||||
|
@ -834,6 +894,7 @@ impl Render for MemoryView {
|
||||||
.on_action(cx.listener(Self::go_to_address))
|
.on_action(cx.listener(Self::go_to_address))
|
||||||
.p_1()
|
.p_1()
|
||||||
.on_action(cx.listener(Self::confirm))
|
.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_down))
|
||||||
.on_action(cx.listener(Self::page_up))
|
.on_action(cx.listener(Self::page_up))
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
|
@ -13,7 +13,10 @@ use gpui::{
|
||||||
uniform_list,
|
uniform_list,
|
||||||
};
|
};
|
||||||
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||||
use project::debugger::session::{Session, SessionEvent, Watcher};
|
use project::debugger::{
|
||||||
|
dap_command::DataBreakpointContext,
|
||||||
|
session::{Session, SessionEvent, Watcher},
|
||||||
|
};
|
||||||
use std::{collections::HashMap, ops::Range, sync::Arc};
|
use std::{collections::HashMap, ops::Range, sync::Arc};
|
||||||
use ui::{ContextMenu, ListItem, ScrollableHandle, Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
use ui::{ContextMenu, ListItem, ScrollableHandle, Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||||
use util::{debug_panic, maybe};
|
use util::{debug_panic, maybe};
|
||||||
|
@ -220,6 +223,7 @@ impl VariableList {
|
||||||
SessionEvent::Variables | SessionEvent::Watchers => {
|
SessionEvent::Variables | SessionEvent::Watchers => {
|
||||||
this.build_entries(cx);
|
this.build_entries(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}),
|
}),
|
||||||
cx.on_focus_out(&focus_handle, window, |this, _, _, cx| {
|
cx.on_focus_out(&focus_handle, window, |this, _, _, cx| {
|
||||||
|
@ -625,22 +629,69 @@ impl VariableList {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let supports_set_variable = self
|
let (supports_set_variable, supports_data_breakpoints, supports_go_to_memory) =
|
||||||
.session
|
self.session.read_with(cx, |session, _| {
|
||||||
.read(cx)
|
(
|
||||||
|
session
|
||||||
.capabilities()
|
.capabilities()
|
||||||
.supports_set_variable
|
.supports_set_variable
|
||||||
.unwrap_or_default();
|
.unwrap_or_default(),
|
||||||
|
session
|
||||||
|
.capabilities()
|
||||||
|
.supports_data_breakpoints
|
||||||
|
.unwrap_or_default(),
|
||||||
|
session
|
||||||
|
.capabilities()
|
||||||
|
.supports_read_memory_request
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let can_toggle_data_breakpoint = entry
|
||||||
|
.as_variable()
|
||||||
|
.filter(|_| supports_data_breakpoints)
|
||||||
|
.and_then(|variable| {
|
||||||
|
let variables_reference = self
|
||||||
|
.entry_states
|
||||||
|
.get(&entry.path)
|
||||||
|
.map(|state| state.parent_reference)?;
|
||||||
|
Some(self.session.update(cx, |session, cx| {
|
||||||
|
session.data_breakpoint_info(
|
||||||
|
Arc::new(DataBreakpointContext::Variable {
|
||||||
|
variables_reference,
|
||||||
|
name: variable.name.clone(),
|
||||||
|
bytes: None,
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
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()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
cx.update(|window, cx| {
|
||||||
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
|
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
|
||||||
menu.when(entry.as_variable().is_some(), |menu| {
|
menu.when_some(entry.as_variable(), |menu, _| {
|
||||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||||
.action("Copy Value", CopyVariableValue.boxed_clone())
|
.action("Copy Value", CopyVariableValue.boxed_clone())
|
||||||
.when(supports_set_variable, |menu| {
|
.when(supports_set_variable, |menu| {
|
||||||
menu.action("Edit Value", EditVariable.boxed_clone())
|
menu.action("Edit Value", EditVariable.boxed_clone())
|
||||||
})
|
})
|
||||||
|
.when(supports_go_to_memory, |menu| {
|
||||||
|
menu.action("Go To Memory", GoToMemory.boxed_clone())
|
||||||
|
})
|
||||||
.action("Watch Variable", AddWatch.boxed_clone())
|
.action("Watch Variable", AddWatch.boxed_clone())
|
||||||
.action("Go To Memory", GoToMemory.boxed_clone())
|
.when(can_toggle_data_breakpoint, |menu| {
|
||||||
|
menu.action(
|
||||||
|
"Toggle Data Breakpoint",
|
||||||
|
crate::ToggleDataBreakpoint.boxed_clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.when(entry.as_watcher().is_some(), |menu| {
|
.when(entry.as_watcher().is_some(), |menu| {
|
||||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||||
|
@ -650,9 +701,10 @@ impl VariableList {
|
||||||
})
|
})
|
||||||
.action("Remove Watch", RemoveWatch.boxed_clone())
|
.action("Remove Watch", RemoveWatch.boxed_clone())
|
||||||
})
|
})
|
||||||
.context(self.focus_handle.clone())
|
.context(focus_handle.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_ = this.update(cx, |this, cx| {
|
||||||
cx.focus_view(&context_menu, window);
|
cx.focus_view(&context_menu, window);
|
||||||
let subscription = cx.subscribe_in(
|
let subscription = cx.subscribe_in(
|
||||||
&context_menu,
|
&context_menu,
|
||||||
|
@ -668,7 +720,65 @@ impl VariableList {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
self.open_context_menu = Some((context_menu, position, subscription));
|
this.open_context_menu = Some((context_menu, position, subscription));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_data_breakpoint(
|
||||||
|
&mut self,
|
||||||
|
_: &crate::ToggleDataBreakpoint,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(entry) = self
|
||||||
|
.selection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|selection| self.entries.iter().find(|entry| &entry.path == selection))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((name, var_ref)) = entry.as_variable().map(|var| &var.name).zip(
|
||||||
|
self.entry_states
|
||||||
|
.get(&entry.path)
|
||||||
|
.map(|state| state.parent_reference),
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = Arc::new(DataBreakpointContext::Variable {
|
||||||
|
variables_reference: var_ref,
|
||||||
|
name: name.clone(),
|
||||||
|
bytes: None,
|
||||||
|
});
|
||||||
|
let data_breakpoint = self.session.update(cx, |session, cx| {
|
||||||
|
session.data_breakpoint_info(context.clone(), None, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let session = self.session.downgrade();
|
||||||
|
cx.spawn(async move |_, cx| {
|
||||||
|
let Some(data_id) = data_breakpoint.await.and_then(|info| info.data_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
_ = session.update(cx, |session, cx| {
|
||||||
|
session.create_data_breakpoint(
|
||||||
|
context,
|
||||||
|
data_id.clone(),
|
||||||
|
dap::DataBreakpoint {
|
||||||
|
data_id,
|
||||||
|
access_type: None,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_variable_name(
|
fn copy_variable_name(
|
||||||
|
@ -1415,6 +1525,7 @@ impl Render for VariableList {
|
||||||
.on_action(cx.listener(Self::edit_variable))
|
.on_action(cx.listener(Self::edit_variable))
|
||||||
.on_action(cx.listener(Self::add_watcher))
|
.on_action(cx.listener(Self::add_watcher))
|
||||||
.on_action(cx.listener(Self::remove_watcher))
|
.on_action(cx.listener(Self::remove_watcher))
|
||||||
|
.on_action(cx.listener(Self::toggle_data_breakpoint))
|
||||||
.on_action(cx.listener(Self::jump_to_variable_memory))
|
.on_action(cx.listener(Self::jump_to_variable_memory))
|
||||||
.child(
|
.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
|
|
|
@ -11,6 +11,7 @@ use dap::{
|
||||||
proto_conversions::ProtoConversion,
|
proto_conversions::ProtoConversion,
|
||||||
requests::{Continue, Next},
|
requests::{Continue, Next},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -813,7 +814,7 @@ impl DapCommand for RestartCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct VariablesCommand {
|
pub struct VariablesCommand {
|
||||||
pub variables_reference: u64,
|
pub variables_reference: u64,
|
||||||
pub filter: Option<VariablesArgumentsFilter>,
|
pub filter: Option<VariablesArgumentsFilter>,
|
||||||
|
@ -1667,6 +1668,130 @@ impl LocalDapCommand for SetBreakpoints {
|
||||||
Ok(message.breakpoints)
|
Ok(message.breakpoints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub enum DataBreakpointContext {
|
||||||
|
Variable {
|
||||||
|
variables_reference: u64,
|
||||||
|
name: String,
|
||||||
|
bytes: Option<u64>,
|
||||||
|
},
|
||||||
|
Expression {
|
||||||
|
expression: String,
|
||||||
|
frame_id: Option<u64>,
|
||||||
|
},
|
||||||
|
Address {
|
||||||
|
address: String,
|
||||||
|
bytes: Option<u64>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataBreakpointContext {
|
||||||
|
pub fn human_readable_label(&self) -> String {
|
||||||
|
match self {
|
||||||
|
DataBreakpointContext::Variable { name, .. } => format!("Variable: {}", name),
|
||||||
|
DataBreakpointContext::Expression { expression, .. } => {
|
||||||
|
format!("Expression: {}", expression)
|
||||||
|
}
|
||||||
|
DataBreakpointContext::Address { address, bytes } => {
|
||||||
|
let mut label = format!("Address: {}", address);
|
||||||
|
if let Some(bytes) = bytes {
|
||||||
|
label.push_str(&format!(
|
||||||
|
" ({} byte{})",
|
||||||
|
bytes,
|
||||||
|
if *bytes == 1 { "" } else { "s" }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub(crate) struct DataBreakpointInfoCommand {
|
||||||
|
pub context: Arc<DataBreakpointContext>,
|
||||||
|
pub mode: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalDapCommand for DataBreakpointInfoCommand {
|
||||||
|
type Response = dap::DataBreakpointInfoResponse;
|
||||||
|
type DapRequest = dap::requests::DataBreakpointInfo;
|
||||||
|
const CACHEABLE: bool = true;
|
||||||
|
|
||||||
|
// todo(debugger): We should expand this trait in the future to take a &self
|
||||||
|
// Depending on this command is_supported could be differentb
|
||||||
|
fn is_supported(capabilities: &Capabilities) -> bool {
|
||||||
|
capabilities.supports_data_breakpoints.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||||
|
let (variables_reference, name, frame_id, as_address, bytes) = match &*self.context {
|
||||||
|
DataBreakpointContext::Variable {
|
||||||
|
variables_reference,
|
||||||
|
name,
|
||||||
|
bytes,
|
||||||
|
} => (
|
||||||
|
Some(*variables_reference),
|
||||||
|
name.clone(),
|
||||||
|
None,
|
||||||
|
Some(false),
|
||||||
|
*bytes,
|
||||||
|
),
|
||||||
|
DataBreakpointContext::Expression {
|
||||||
|
expression,
|
||||||
|
frame_id,
|
||||||
|
} => (None, expression.clone(), *frame_id, Some(false), None),
|
||||||
|
DataBreakpointContext::Address { address, bytes } => {
|
||||||
|
(None, address.clone(), None, Some(true), *bytes)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dap::DataBreakpointInfoArguments {
|
||||||
|
variables_reference,
|
||||||
|
name,
|
||||||
|
frame_id,
|
||||||
|
bytes,
|
||||||
|
as_address,
|
||||||
|
mode: self.mode.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_from_dap(
|
||||||
|
&self,
|
||||||
|
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||||
|
) -> Result<Self::Response> {
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
pub(crate) struct SetDataBreakpointsCommand {
|
||||||
|
pub breakpoints: Vec<dap::DataBreakpoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalDapCommand for SetDataBreakpointsCommand {
|
||||||
|
type Response = Vec<dap::Breakpoint>;
|
||||||
|
type DapRequest = dap::requests::SetDataBreakpoints;
|
||||||
|
|
||||||
|
fn is_supported(capabilities: &Capabilities) -> bool {
|
||||||
|
capabilities.supports_data_breakpoints.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||||
|
dap::SetDataBreakpointsArguments {
|
||||||
|
breakpoints: self.breakpoints.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_from_dap(
|
||||||
|
&self,
|
||||||
|
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||||
|
) -> Result<Self::Response> {
|
||||||
|
Ok(message.breakpoints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||||
pub(super) enum SetExceptionBreakpoints {
|
pub(super) enum SetExceptionBreakpoints {
|
||||||
Plain {
|
Plain {
|
||||||
|
@ -1776,7 +1901,7 @@ impl DapCommand for LocationsCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub(crate) struct ReadMemory {
|
pub(crate) struct ReadMemory {
|
||||||
pub(crate) memory_reference: String,
|
pub(crate) memory_reference: String,
|
||||||
pub(crate) offset: Option<u64>,
|
pub(crate) offset: Option<u64>,
|
||||||
|
@ -1829,25 +1954,6 @@ impl LocalDapCommand for ReadMemory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalDapCommand for dap::DataBreakpointInfoArguments {
|
|
||||||
type Response = dap::DataBreakpointInfoResponse;
|
|
||||||
type DapRequest = dap::requests::DataBreakpointInfo;
|
|
||||||
const CACHEABLE: bool = true;
|
|
||||||
fn is_supported(capabilities: &Capabilities) -> bool {
|
|
||||||
capabilities.supports_data_breakpoints.unwrap_or_default()
|
|
||||||
}
|
|
||||||
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn response_from_dap(
|
|
||||||
&self,
|
|
||||||
message: <Self::DapRequest as dap::requests::Request>::Response,
|
|
||||||
) -> Result<Self::Response> {
|
|
||||||
Ok(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalDapCommand for dap::WriteMemoryArguments {
|
impl LocalDapCommand for dap::WriteMemoryArguments {
|
||||||
type Response = dap::WriteMemoryResponse;
|
type Response = dap::WriteMemoryResponse;
|
||||||
type DapRequest = dap::requests::WriteMemory;
|
type DapRequest = dap::requests::WriteMemory;
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::debugger::breakpoint_store::BreakpointSessionState;
|
use crate::debugger::breakpoint_store::BreakpointSessionState;
|
||||||
use crate::debugger::dap_command::ReadMemory;
|
use crate::debugger::dap_command::{DataBreakpointContext, ReadMemory};
|
||||||
use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress};
|
use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress};
|
||||||
|
|
||||||
use super::breakpoint_store::{
|
use super::breakpoint_store::{
|
||||||
BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
|
BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
|
||||||
};
|
};
|
||||||
use super::dap_command::{
|
use super::dap_command::{
|
||||||
self, Attach, ConfigurationDone, ContinueCommand, DisconnectCommand, EvaluateCommand,
|
self, Attach, ConfigurationDone, ContinueCommand, DataBreakpointInfoCommand, DisconnectCommand,
|
||||||
Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand, ModulesCommand,
|
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
||||||
NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand,
|
ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
|
||||||
SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand, StepBackCommand,
|
ScopesCommand, SetDataBreakpointsCommand, SetExceptionBreakpoints, SetVariableValueCommand,
|
||||||
StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand,
|
StackTraceCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand,
|
||||||
ThreadsCommand, VariablesCommand,
|
TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
|
||||||
};
|
};
|
||||||
use super::dap_store::DapStore;
|
use super::dap_store::DapStore;
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
|
@ -138,6 +138,13 @@ pub struct Watcher {
|
||||||
pub presentation_hint: Option<VariablePresentationHint>,
|
pub presentation_hint: Option<VariablePresentationHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct DataBreakpointState {
|
||||||
|
pub dap: dap::DataBreakpoint,
|
||||||
|
pub is_enabled: bool,
|
||||||
|
pub context: Arc<DataBreakpointContext>,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum SessionState {
|
pub enum SessionState {
|
||||||
Building(Option<Task<Result<()>>>),
|
Building(Option<Task<Result<()>>>),
|
||||||
Running(RunningMode),
|
Running(RunningMode),
|
||||||
|
@ -686,6 +693,7 @@ pub struct Session {
|
||||||
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
||||||
ignore_breakpoints: bool,
|
ignore_breakpoints: bool,
|
||||||
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
||||||
|
data_breakpoints: BTreeMap<String, DataBreakpointState>,
|
||||||
background_tasks: Vec<Task<()>>,
|
background_tasks: Vec<Task<()>>,
|
||||||
restart_task: Option<Task<()>>,
|
restart_task: Option<Task<()>>,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
|
@ -780,6 +788,7 @@ pub enum SessionEvent {
|
||||||
request: RunInTerminalRequestArguments,
|
request: RunInTerminalRequestArguments,
|
||||||
sender: mpsc::Sender<Result<u32>>,
|
sender: mpsc::Sender<Result<u32>>,
|
||||||
},
|
},
|
||||||
|
DataBreakpointInfo,
|
||||||
ConsoleOutput,
|
ConsoleOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -856,6 +865,7 @@ impl Session {
|
||||||
is_session_terminated: false,
|
is_session_terminated: false,
|
||||||
ignore_breakpoints: false,
|
ignore_breakpoints: false,
|
||||||
breakpoint_store,
|
breakpoint_store,
|
||||||
|
data_breakpoints: Default::default(),
|
||||||
exception_breakpoints: Default::default(),
|
exception_breakpoints: Default::default(),
|
||||||
label,
|
label,
|
||||||
adapter,
|
adapter,
|
||||||
|
@ -1670,6 +1680,7 @@ impl Session {
|
||||||
self.invalidate_command_type::<ModulesCommand>();
|
self.invalidate_command_type::<ModulesCommand>();
|
||||||
self.invalidate_command_type::<LoadedSourcesCommand>();
|
self.invalidate_command_type::<LoadedSourcesCommand>();
|
||||||
self.invalidate_command_type::<ThreadsCommand>();
|
self.invalidate_command_type::<ThreadsCommand>();
|
||||||
|
self.invalidate_command_type::<DataBreakpointInfoCommand>();
|
||||||
self.invalidate_command_type::<ReadMemory>();
|
self.invalidate_command_type::<ReadMemory>();
|
||||||
let executor = self.as_running().map(|running| running.executor.clone());
|
let executor = self.as_running().map(|running| running.executor.clone());
|
||||||
if let Some(executor) = executor {
|
if let Some(executor) = executor {
|
||||||
|
@ -1906,6 +1917,10 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn data_breakpoints(&self) -> impl Iterator<Item = &DataBreakpointState> {
|
||||||
|
self.data_breakpoints.values()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exception_breakpoints(
|
pub fn exception_breakpoints(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
|
) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
|
||||||
|
@ -1939,6 +1954,45 @@ impl Session {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<'_, Session>) {
|
||||||
|
if let Some(state) = self.data_breakpoints.get_mut(id) {
|
||||||
|
state.is_enabled = !state.is_enabled;
|
||||||
|
self.send_exception_breakpoints(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_data_breakpoints(&mut self, cx: &mut Context<Self>) {
|
||||||
|
if let Some(mode) = self.as_running() {
|
||||||
|
let breakpoints = self
|
||||||
|
.data_breakpoints
|
||||||
|
.values()
|
||||||
|
.filter_map(|state| state.is_enabled.then(|| state.dap.clone()))
|
||||||
|
.collect();
|
||||||
|
let command = SetDataBreakpointsCommand { breakpoints };
|
||||||
|
mode.request(command).detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_data_breakpoint(
|
||||||
|
&mut self,
|
||||||
|
context: Arc<DataBreakpointContext>,
|
||||||
|
data_id: String,
|
||||||
|
dap: dap::DataBreakpoint,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if self.data_breakpoints.remove(&data_id).is_none() {
|
||||||
|
self.data_breakpoints.insert(
|
||||||
|
data_id,
|
||||||
|
DataBreakpointState {
|
||||||
|
dap,
|
||||||
|
is_enabled: true,
|
||||||
|
context,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.send_data_breakpoints(cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn breakpoints_enabled(&self) -> bool {
|
pub fn breakpoints_enabled(&self) -> bool {
|
||||||
self.ignore_breakpoints
|
self.ignore_breakpoints
|
||||||
}
|
}
|
||||||
|
@ -2500,6 +2554,20 @@ impl Session {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn data_breakpoint_info(
|
||||||
|
&mut self,
|
||||||
|
context: Arc<DataBreakpointContext>,
|
||||||
|
mode: Option<String>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Option<dap::DataBreakpointInfoResponse>> {
|
||||||
|
let command = DataBreakpointInfoCommand {
|
||||||
|
context: context.clone(),
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.request(command, |_, response, _| response.ok(), cx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_variable_value(
|
pub fn set_variable_value(
|
||||||
&mut self,
|
&mut self,
|
||||||
stack_frame_id: u64,
|
stack_frame_id: u64,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue