Detailed changes
@@ -1472,8 +1472,10 @@ impl Render for DebugPanel {
h_flex().size_full()
.items_start()
- .child(v_flex().items_start().min_w_1_3().h_full().p_1()
- .child(h_flex().px_1().child(Label::new("Breakpoints").size(LabelSize::Small)))
+ .child(v_flex().group("base-breakpoint-list").items_start().min_w_1_3().h_full().p_1()
+ .child(h_flex().pl_1().w_full().justify_between()
+ .child(Label::new("Breakpoints").size(LabelSize::Small))
+ .child(h_flex().visible_on_hover("base-breakpoint-list").child(self.breakpoint_list.read(cx).render_control_strip())))
.child(Divider::horizontal())
.child(self.breakpoint_list.clone()))
.child(Divider::vertical())
@@ -265,56 +265,37 @@ pub(crate) fn deserialize_pane_layout(
stack_frame_list.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
- None,
cx,
)),
DebuggerPaneItem::Variables => Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
DebuggerPaneItem::Variables,
- None,
- cx,
- )),
- DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
- breakpoint_list.focus_handle(cx),
- breakpoint_list.clone().into(),
- DebuggerPaneItem::BreakpointList,
- None,
cx,
)),
+ DebuggerPaneItem::BreakpointList => {
+ Box::new(SubView::breakpoint_list(breakpoint_list.clone(), cx))
+ }
DebuggerPaneItem::Modules => Box::new(SubView::new(
module_list.focus_handle(cx),
module_list.clone().into(),
DebuggerPaneItem::Modules,
- None,
cx,
)),
DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
loaded_sources.focus_handle(cx),
loaded_sources.clone().into(),
DebuggerPaneItem::LoadedSources,
- None,
- cx,
- )),
- DebuggerPaneItem::Console => Box::new(SubView::new(
- console.focus_handle(cx),
- console.clone().into(),
- DebuggerPaneItem::Console,
- Some(Box::new({
- let console = console.clone().downgrade();
- move |cx| {
- console
- .read_with(cx, |console, cx| console.show_indicator(cx))
- .unwrap_or_default()
- }
- })),
cx,
)),
+ DebuggerPaneItem::Console => {
+ let view = SubView::console(console.clone(), cx);
+ Box::new(view)
+ }
DebuggerPaneItem::Terminal => Box::new(SubView::new(
terminal.focus_handle(cx),
terminal.clone().into(),
DebuggerPaneItem::Terminal,
- None,
cx,
)),
})
@@ -135,6 +135,7 @@ pub(crate) struct SubView {
item_focus_handle: FocusHandle,
kind: DebuggerPaneItem,
show_indicator: Box<dyn Fn(&App) -> bool>,
+ actions: Option<Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>>,
hovered: bool,
}
@@ -143,21 +144,68 @@ impl SubView {
item_focus_handle: FocusHandle,
view: AnyView,
kind: DebuggerPaneItem,
- show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
cx: &mut App,
) -> Entity<Self> {
cx.new(|_| Self {
kind,
inner: view,
item_focus_handle,
- show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
+ show_indicator: Box::new(|_| false),
+ actions: None,
hovered: false,
})
}
+ pub(crate) fn console(console: Entity<Console>, cx: &mut App) -> Entity<Self> {
+ let weak_console = console.downgrade();
+ let this = Self::new(
+ console.focus_handle(cx),
+ console.into(),
+ DebuggerPaneItem::Console,
+ cx,
+ );
+ this.update(cx, |this, _| {
+ this.with_indicator(Box::new(move |cx| {
+ weak_console
+ .read_with(cx, |console, cx| console.show_indicator(cx))
+ .unwrap_or_default()
+ }))
+ });
+ this
+ }
+
+ pub(crate) fn breakpoint_list(list: Entity<BreakpointList>, cx: &mut App) -> Entity<Self> {
+ let weak_list = list.downgrade();
+ let focus_handle = list.focus_handle(cx);
+ let this = Self::new(
+ focus_handle.clone(),
+ list.into(),
+ DebuggerPaneItem::BreakpointList,
+ cx,
+ );
+
+ this.update(cx, |this, _| {
+ this.with_actions(Box::new(move |_, cx| {
+ weak_list
+ .update(cx, |this, _| this.render_control_strip())
+ .unwrap_or_else(|_| div().into_any_element())
+ }));
+ });
+ this
+ }
+
pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
self.kind
}
+ pub(crate) fn with_indicator(&mut self, indicator: Box<dyn Fn(&App) -> bool>) {
+ self.show_indicator = indicator;
+ }
+ pub(crate) fn with_actions(
+ &mut self,
+ actions: Box<dyn FnMut(&mut Window, &mut App) -> AnyElement>,
+ ) {
+ self.actions = Some(actions);
+ }
}
impl Focusable for SubView {
fn focus_handle(&self, _: &App) -> FocusHandle {
@@ -359,10 +407,13 @@ pub(crate) fn new_debugger_pane(
let active_pane_item = pane.active_item();
let pane_group_id: SharedString =
format!("pane-zoom-button-hover-{}", cx.entity_id()).into();
- let is_hovered = active_pane_item.as_ref().map_or(false, |item| {
- item.downcast::<SubView>()
- .map_or(false, |this| this.read(cx).hovered)
- });
+ let as_subview = active_pane_item
+ .as_ref()
+ .and_then(|item| item.downcast::<SubView>());
+ let is_hovered = as_subview
+ .as_ref()
+ .map_or(false, |item| item.read(cx).hovered);
+
h_flex()
.group(pane_group_id.clone())
.justify_between()
@@ -459,9 +510,17 @@ pub(crate) fn new_debugger_pane(
)
.child({
let zoomed = pane.is_zoomed();
- div()
+ h_flex()
.visible_on_hover(pane_group_id)
.when(is_hovered, |this| this.visible())
+ .when_some(as_subview.as_ref(), |this, subview| {
+ subview.update(cx, |view, cx| {
+ let Some(additional_actions) = view.actions.as_mut() else {
+ return this;
+ };
+ this.child(additional_actions(window, cx))
+ })
+ })
.child(
IconButton::new(
SharedString::from(format!(
@@ -1095,61 +1154,38 @@ impl RunningState {
cx: &mut Context<Self>,
) -> Box<dyn ItemHandle> {
match item_kind {
- DebuggerPaneItem::Console => {
- let weak_console = self.console.clone().downgrade();
-
- Box::new(SubView::new(
- self.console.focus_handle(cx),
- self.console.clone().into(),
- item_kind,
- Some(Box::new(move |cx| {
- weak_console
- .read_with(cx, |console, cx| console.show_indicator(cx))
- .unwrap_or_default()
- })),
- cx,
- ))
- }
+ DebuggerPaneItem::Console => Box::new(SubView::console(self.console.clone(), cx)),
DebuggerPaneItem::Variables => Box::new(SubView::new(
self.variable_list.focus_handle(cx),
self.variable_list.clone().into(),
item_kind,
- None,
- cx,
- )),
- DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
- self.breakpoint_list.focus_handle(cx),
- self.breakpoint_list.clone().into(),
- item_kind,
- None,
cx,
)),
+ DebuggerPaneItem::BreakpointList => {
+ Box::new(SubView::breakpoint_list(self.breakpoint_list.clone(), cx))
+ }
DebuggerPaneItem::Frames => Box::new(SubView::new(
self.stack_frame_list.focus_handle(cx),
self.stack_frame_list.clone().into(),
item_kind,
- None,
cx,
)),
DebuggerPaneItem::Modules => Box::new(SubView::new(
self.module_list.focus_handle(cx),
self.module_list.clone().into(),
item_kind,
- None,
cx,
)),
DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
self.loaded_sources_list.focus_handle(cx),
self.loaded_sources_list.clone().into(),
item_kind,
- None,
cx,
)),
DebuggerPaneItem::Terminal => Box::new(SubView::new(
self.debug_terminal.focus_handle(cx),
self.debug_terminal.clone().into(),
item_kind,
- None,
cx,
)),
}
@@ -1558,7 +1594,6 @@ impl RunningState {
this.focus_handle(cx),
stack_frame_list.clone().into(),
DebuggerPaneItem::Frames,
- None,
cx,
)),
true,
@@ -1568,13 +1603,7 @@ impl RunningState {
cx,
);
this.add_item(
- Box::new(SubView::new(
- breakpoints.focus_handle(cx),
- breakpoints.clone().into(),
- DebuggerPaneItem::BreakpointList,
- None,
- cx,
- )),
+ Box::new(SubView::breakpoint_list(breakpoints.clone(), cx)),
true,
false,
None,
@@ -1586,32 +1615,15 @@ impl RunningState {
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
center_pane.update(cx, |this, cx| {
- let weak_console = console.downgrade();
- this.add_item(
- Box::new(SubView::new(
- console.focus_handle(cx),
- console.clone().into(),
- DebuggerPaneItem::Console,
- Some(Box::new(move |cx| {
- weak_console
- .read_with(cx, |console, cx| console.show_indicator(cx))
- .unwrap_or_default()
- })),
- cx,
- )),
- true,
- false,
- None,
- window,
- cx,
- );
+ let view = SubView::console(console.clone(), cx);
+
+ this.add_item(Box::new(view), true, false, None, window, cx);
this.add_item(
Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
DebuggerPaneItem::Variables,
- None,
cx,
)),
true,
@@ -1630,7 +1642,6 @@ impl RunningState {
debug_terminal.focus_handle(cx),
debug_terminal.clone().into(),
DebuggerPaneItem::Terminal,
- None,
cx,
)),
false,
@@ -8,8 +8,8 @@ use std::{
use dap::ExceptionBreakpointsFilter;
use editor::Editor;
use gpui::{
- AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful, Task,
- UniformListScrollHandle, WeakEntity, uniform_list,
+ Action, AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful,
+ Task, UniformListScrollHandle, WeakEntity, uniform_list,
};
use language::Point;
use project::{
@@ -21,15 +21,21 @@ use project::{
worktree_store::WorktreeStore,
};
use ui::{
- App, ButtonCommon, Clickable, Color, Context, Div, FluentBuilder as _, Icon, IconButton,
- IconName, Indicator, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem,
- ParentElement, Render, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement,
- Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex,
+ AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div, FluentBuilder as _,
+ Icon, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label,
+ LabelCommon, LabelSize, ListItem, ParentElement, Render, Scrollbar, ScrollbarState,
+ SharedString, StatefulInteractiveElement, Styled, Toggleable, Tooltip, Window, div, h_flex, px,
+ v_flex,
};
use util::ResultExt;
use workspace::Workspace;
use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
+#[derive(Clone, Copy, PartialEq)]
+pub(crate) enum SelectedBreakpointKind {
+ Source,
+ Exception,
+}
pub(crate) struct BreakpointList {
workspace: WeakEntity<Workspace>,
breakpoint_store: Entity<BreakpointStore>,
@@ -127,6 +133,21 @@ impl BreakpointList {
.detach();
}
+ pub(crate) fn selection_kind(&self) -> Option<(SelectedBreakpointKind, bool)> {
+ self.selected_ix.and_then(|ix| {
+ self.breakpoints.get(ix).map(|bp| match &bp.kind {
+ BreakpointEntryKind::LineBreakpoint(bp) => (
+ SelectedBreakpointKind::Source,
+ bp.breakpoint.state
+ == project::debugger::breakpoint_store::BreakpointState::Enabled,
+ ),
+ BreakpointEntryKind::ExceptionBreakpoint(bp) => {
+ (SelectedBreakpointKind::Exception, bp.is_enabled)
+ }
+ })
+ })
+ }
+
fn select_ix(&mut self, ix: Option<usize>, cx: &mut Context<Self>) {
self.selected_ix = ix;
if let Some(ix) = ix {
@@ -333,7 +354,93 @@ impl BreakpointList {
.children(Scrollbar::vertical(self.scrollbar_state.clone())),
)
}
+ pub(crate) fn render_control_strip(&self) -> AnyElement {
+ let selection_kind = self.selection_kind();
+ let focus_handle = self.focus_handle.clone();
+ let remove_breakpoint_tooltip = selection_kind.map(|(kind, _)| match kind {
+ SelectedBreakpointKind::Source => "Remove breakpoint from a breakpoint list",
+ SelectedBreakpointKind::Exception => {
+ "Exception Breakpoints cannot be removed from the breakpoint list"
+ }
+ });
+ let toggle_label = selection_kind.map(|(_, is_enabled)| {
+ if is_enabled {
+ (
+ "Disable Breakpoint",
+ "Disable a breakpoint without removing it from the list",
+ )
+ } else {
+ ("Enable Breakpoint", "Re-enable a breakpoint")
+ }
+ });
+
+ h_flex()
+ .gap_2()
+ .child(
+ IconButton::new(
+ "disable-breakpoint-breakpoint-list",
+ IconName::DebugDisabledBreakpoint,
+ )
+ .icon_size(IconSize::XSmall)
+ .when_some(toggle_label, |this, (label, meta)| {
+ this.tooltip({
+ let focus_handle = focus_handle.clone();
+ move |window, cx| {
+ Tooltip::with_meta_in(
+ label,
+ Some(&ToggleEnableBreakpoint),
+ meta,
+ &focus_handle,
+ window,
+ cx,
+ )
+ }
+ })
+ })
+ .disabled(selection_kind.is_none())
+ .on_click({
+ let focus_handle = focus_handle.clone();
+ move |_, window, cx| {
+ focus_handle.focus(window);
+ window.dispatch_action(ToggleEnableBreakpoint.boxed_clone(), cx)
+ }
+ }),
+ )
+ .child(
+ IconButton::new("remove-breakpoint-breakpoint-list", IconName::X)
+ .icon_size(IconSize::XSmall)
+ .icon_color(ui::Color::Error)
+ .when_some(remove_breakpoint_tooltip, |this, tooltip| {
+ this.tooltip({
+ let focus_handle = focus_handle.clone();
+ move |window, cx| {
+ Tooltip::with_meta_in(
+ "Remove Breakpoint",
+ Some(&UnsetBreakpoint),
+ tooltip,
+ &focus_handle,
+ window,
+ cx,
+ )
+ }
+ })
+ })
+ .disabled(
+ selection_kind.map(|kind| kind.0) != Some(SelectedBreakpointKind::Source),
+ )
+ .on_click({
+ let focus_handle = focus_handle.clone();
+ move |_, window, cx| {
+ focus_handle.focus(window);
+ window.dispatch_action(UnsetBreakpoint.boxed_clone(), cx)
+ }
+ }),
+ )
+ .mr_2()
+ .into_any_element()
+ }
}
+
impl Render for BreakpointList {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
// let old_len = self.breakpoints.len();
@@ -505,44 +612,6 @@ impl LineBreakpoint {
.on_secondary_mouse_down(|_, _, cx| {
cx.stop_propagation();
})
- .end_hover_slot(
- h_flex()
- .child(
- IconButton::new(
- SharedString::from(format!(
- "breakpoint-ui-on-click-go-to-line-remove-{:?}/{}:{}",
- self.dir, self.name, self.line
- )),
- IconName::Close,
- )
- .on_click({
- let weak = weak.clone();
- let path = path.clone();
- move |_, _, cx| {
- weak.update(cx, |breakpoint_list, cx| {
- breakpoint_list.edit_line_breakpoint(
- path.clone(),
- row,
- BreakpointEditAction::Toggle,
- cx,
- );
- })
- .ok();
- }
- })
- .tooltip(move |window, cx| {
- Tooltip::for_action_in(
- "Unset Breakpoint",
- &UnsetBreakpoint,
- &focus_handle,
- window,
- cx,
- )
- })
- .icon_size(ui::IconSize::XSmall),
- )
- .right_4(),
- )
.child(
v_flex()
.py_1()