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,
|
||||
/// Toggles expansion of the selected item in the debugger UI.
|
||||
ToggleExpandItem,
|
||||
/// Set a data breakpoint on the selected variable or memory region.
|
||||
ToggleDataBreakpoint,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@ use project::{
|
|||
};
|
||||
use ui::{
|
||||
ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div,
|
||||
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, Indicator,
|
||||
InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement,
|
||||
Render, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
|
||||
Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
|
||||
Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, InteractiveElement,
|
||||
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Toggleable,
|
||||
Tooltip, Window, div, h_flex, px, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
@ -46,6 +46,7 @@ actions!(
|
|||
pub(crate) enum SelectedBreakpointKind {
|
||||
Source,
|
||||
Exception,
|
||||
Data,
|
||||
}
|
||||
pub(crate) struct BreakpointList {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
|
@ -188,6 +189,9 @@ impl BreakpointList {
|
|||
BreakpointEntryKind::ExceptionBreakpoint(bp) => {
|
||||
(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;
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
@ -441,7 +450,7 @@ impl BreakpointList {
|
|||
let row = line_breakpoint.breakpoint.row;
|
||||
self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx);
|
||||
}
|
||||
BreakpointEntryKind::ExceptionBreakpoint(_) => {}
|
||||
_ => {}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -490,6 +499,14 @@ impl BreakpointList {
|
|||
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>) {
|
||||
if let Some(session) = &self.session {
|
||||
session.update(cx, |this, cx| {
|
||||
|
@ -642,6 +659,7 @@ impl BreakpointList {
|
|||
SelectedBreakpointKind::Exception => {
|
||||
"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)| {
|
||||
if is_enabled {
|
||||
|
@ -783,8 +801,20 @@ impl Render for BreakpointList {
|
|||
weak: weak.clone(),
|
||||
})
|
||||
});
|
||||
self.breakpoints
|
||||
.extend(breakpoints.chain(exception_breakpoints));
|
||||
let data_breakpoints = self.session.as_ref().into_iter().flat_map(|session| {
|
||||
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()
|
||||
.id("breakpoint-list")
|
||||
.key_context("BreakpointList")
|
||||
|
@ -905,7 +935,11 @@ impl LineBreakpoint {
|
|||
.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 |_, _, _| {});
|
||||
|
||||
ListItem::new(SharedString::from(format!(
|
||||
|
@ -996,6 +1030,103 @@ struct ExceptionBreakpoint {
|
|||
data: ExceptionBreakpointsFilter,
|
||||
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 {
|
||||
fn render(
|
||||
|
@ -1062,7 +1193,11 @@ impl ExceptionBreakpoint {
|
|||
}
|
||||
})
|
||||
.cursor_pointer()
|
||||
.child(Indicator::icon(Icon::new(IconName::Flame)).color(color)),
|
||||
.child(
|
||||
Icon::new(IconName::Flame)
|
||||
.color(color)
|
||||
.size(IconSize::Small),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
@ -1105,6 +1240,7 @@ impl ExceptionBreakpoint {
|
|||
enum BreakpointEntryKind {
|
||||
LineBreakpoint(LineBreakpoint),
|
||||
ExceptionBreakpoint(ExceptionBreakpoint),
|
||||
DataBreakpoint(DataBreakpoint),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -1140,6 +1276,14 @@ impl BreakpointEntry {
|
|||
focus_handle,
|
||||
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
|
||||
)
|
||||
.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) => {
|
||||
line_breakpoint.breakpoint.condition.is_some()
|
||||
}
|
||||
// We don't support conditions on exception breakpoints
|
||||
BreakpointEntryKind::ExceptionBreakpoint(_) => false,
|
||||
// We don't support conditions on exception/data breakpoints
|
||||
_ => 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.
|
||||
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)]
|
||||
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 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()
|
||||
|
|
|
@ -13,7 +13,10 @@ use gpui::{
|
|||
uniform_list,
|
||||
};
|
||||
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 ui::{ContextMenu, ListItem, ScrollableHandle, Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||
use util::{debug_panic, maybe};
|
||||
|
@ -220,6 +223,7 @@ impl VariableList {
|
|||
SessionEvent::Variables | SessionEvent::Watchers => {
|
||||
this.build_entries(cx);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}),
|
||||
cx.on_focus_out(&focus_handle, window, |this, _, _, cx| {
|
||||
|
@ -625,50 +629,156 @@ impl VariableList {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let supports_set_variable = self
|
||||
.session
|
||||
.read(cx)
|
||||
.capabilities()
|
||||
.supports_set_variable
|
||||
.unwrap_or_default();
|
||||
let (supports_set_variable, supports_data_breakpoints, supports_go_to_memory) =
|
||||
self.session.read_with(cx, |session, _| {
|
||||
(
|
||||
session
|
||||
.capabilities()
|
||||
.supports_set_variable
|
||||
.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 context_menu = ContextMenu::build(window, cx, |menu, _, _| {
|
||||
menu.when(entry.as_variable().is_some(), |menu| {
|
||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||
.action("Copy Value", CopyVariableValue.boxed_clone())
|
||||
.when(supports_set_variable, |menu| {
|
||||
menu.action("Edit Value", EditVariable.boxed_clone())
|
||||
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, _, _| {
|
||||
menu.when_some(entry.as_variable(), |menu, _| {
|
||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||
.action("Copy Value", CopyVariableValue.boxed_clone())
|
||||
.when(supports_set_variable, |menu| {
|
||||
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())
|
||||
.when(can_toggle_data_breakpoint, |menu| {
|
||||
menu.action(
|
||||
"Toggle Data Breakpoint",
|
||||
crate::ToggleDataBreakpoint.boxed_clone(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.action("Watch Variable", AddWatch.boxed_clone())
|
||||
.action("Go To Memory", GoToMemory.boxed_clone())
|
||||
})
|
||||
.when(entry.as_watcher().is_some(), |menu| {
|
||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||
.action("Copy Value", CopyVariableValue.boxed_clone())
|
||||
.when(supports_set_variable, |menu| {
|
||||
menu.action("Edit Value", EditVariable.boxed_clone())
|
||||
.when(entry.as_watcher().is_some(), |menu| {
|
||||
menu.action("Copy Name", CopyVariableName.boxed_clone())
|
||||
.action("Copy Value", CopyVariableValue.boxed_clone())
|
||||
.when(supports_set_variable, |menu| {
|
||||
menu.action("Edit Value", EditVariable.boxed_clone())
|
||||
})
|
||||
.action("Remove Watch", RemoveWatch.boxed_clone())
|
||||
})
|
||||
.action("Remove Watch", RemoveWatch.boxed_clone())
|
||||
.context(focus_handle.clone())
|
||||
});
|
||||
|
||||
_ = this.update(cx, |this, cx| {
|
||||
cx.focus_view(&context_menu, window);
|
||||
let subscription = cx.subscribe_in(
|
||||
&context_menu,
|
||||
window,
|
||||
|this, _, _: &DismissEvent, window, cx| {
|
||||
if this.open_context_menu.as_ref().is_some_and(|context_menu| {
|
||||
context_menu.0.focus_handle(cx).contains_focused(window, cx)
|
||||
}) {
|
||||
cx.focus_self(window);
|
||||
}
|
||||
this.open_context_menu.take();
|
||||
cx.notify();
|
||||
},
|
||||
);
|
||||
|
||||
this.open_context_menu = Some((context_menu, position, subscription));
|
||||
});
|
||||
})
|
||||
.context(self.focus_handle.clone())
|
||||
})
|
||||
.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)
|
||||
});
|
||||
|
||||
cx.focus_view(&context_menu, window);
|
||||
let subscription = cx.subscribe_in(
|
||||
&context_menu,
|
||||
window,
|
||||
|this, _, _: &DismissEvent, window, cx| {
|
||||
if this.open_context_menu.as_ref().is_some_and(|context_menu| {
|
||||
context_menu.0.focus_handle(cx).contains_focused(window, cx)
|
||||
}) {
|
||||
cx.focus_self(window);
|
||||
}
|
||||
this.open_context_menu.take();
|
||||
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();
|
||||
},
|
||||
);
|
||||
|
||||
self.open_context_menu = Some((context_menu, position, subscription));
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn copy_variable_name(
|
||||
|
@ -1415,6 +1525,7 @@ impl Render for VariableList {
|
|||
.on_action(cx.listener(Self::edit_variable))
|
||||
.on_action(cx.listener(Self::add_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))
|
||||
.child(
|
||||
uniform_list(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue