diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 12f0c78b4333bb7c79d4266d9109ee1c396e5f31..2bea91b2dc1b2c928a23ddf101000f2c5a333ffe 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -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()) diff --git a/crates/debugger_ui/src/persistence.rs b/crates/debugger_ui/src/persistence.rs index 79d17116e4af8ed7b5ea2245348d95f3703ebb55..d15244c3496b5cb42bf1b4151e075f624862aec0 100644 --- a/crates/debugger_ui/src/persistence.rs +++ b/crates/debugger_ui/src/persistence.rs @@ -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, )), }) diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 8b7876afdb6493dd4d62663773454837cc346219..d45b9b6e975aa519eda982ea59a5484c7bba3412 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -135,6 +135,7 @@ pub(crate) struct SubView { item_focus_handle: FocusHandle, kind: DebuggerPaneItem, show_indicator: Box bool>, + actions: Option AnyElement>>, hovered: bool, } @@ -143,21 +144,68 @@ impl SubView { item_focus_handle: FocusHandle, view: AnyView, kind: DebuggerPaneItem, - show_indicator: Option bool>>, cx: &mut App, ) -> Entity { 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, cx: &mut App) -> Entity { + 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, cx: &mut App) -> Entity { + 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 bool>) { + self.show_indicator = indicator; + } + pub(crate) fn with_actions( + &mut self, + actions: Box 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::() - .map_or(false, |this| this.read(cx).hovered) - }); + let as_subview = active_pane_item + .as_ref() + .and_then(|item| item.downcast::()); + 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, ) -> Box { 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, diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index c0fe55155a35942980380d1221f90b373538dba0..8077b289a7d111cbbd1ed189206904d753ae412c 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -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, breakpoint_store: Entity, @@ -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, cx: &mut Context) { 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) -> 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()