From b2a92097ee2244ba9c755a787003df9fcf214921 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 21 May 2025 20:56:39 -0400 Subject: [PATCH] debugger: Add actions and keybindings for opening the thread and session menus (#31135) Makes it possible to open and navigate these menus from the keyboard. I also removed the eager previewing behavior for the thread picker, which was buggy and came with a jarring layout shift. Release Notes: - Debugger Beta: Added the `debugger: open thread picker` and `debugger: open session picker` actions. --- assets/keymaps/default-linux.json | 7 +++ assets/keymaps/default-macos.json | 7 +++ crates/debugger_ui/src/debugger_panel.rs | 36 ++++++++++++++- crates/debugger_ui/src/debugger_ui.rs | 2 + crates/debugger_ui/src/dropdown_menus.rs | 8 ++-- crates/debugger_ui/src/session/running.rs | 4 +- crates/ui/src/components/context_menu.rs | 53 ++--------------------- crates/ui/src/components/dropdown_menu.rs | 27 +++++++----- 8 files changed, 76 insertions(+), 68 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 7f02488407..43f00af640 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -862,6 +862,13 @@ "alt-l": "git::GenerateCommitMessage" } }, + { + "context": "DebugPanel", + "bindings": { + "ctrl-t": "debugger::ToggleThreadPicker", + "ctrl-i": "debugger::ToggleSessionPicker" + } + }, { "context": "CollabPanel && not_editing", "bindings": { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 817408fda1..662b8034f4 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -929,6 +929,13 @@ "alt-tab": "git::GenerateCommitMessage" } }, + { + "context": "DebugPanel", + "bindings": { + "cmd-t": "debugger::ToggleThreadPicker", + "cmd-i": "debugger::ToggleSessionPicker" + } + }, { "context": "CollabPanel && not_editing", "use_key_equivalents": true, diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b95303ea87..8dd11ddbf0 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -5,7 +5,7 @@ use crate::{ ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, - persistence, + ToggleSessionPicker, ToggleThreadPicker, persistence, }; use anyhow::{Context as _, Result, anyhow}; use command_palette_hooks::CommandPaletteFilter; @@ -31,7 +31,7 @@ use settings::Settings; use std::any::TypeId; use std::sync::Arc; use task::{DebugScenario, TaskContext}; -use ui::{ContextMenu, Divider, Tooltip, prelude::*}; +use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*}; use workspace::SplitDirection; use workspace::{ Pane, Workspace, @@ -65,6 +65,8 @@ pub struct DebugPanel { workspace: WeakEntity, focus_handle: FocusHandle, context_menu: Option<(Entity, Point, Subscription)>, + pub(crate) thread_picker_menu_handle: PopoverMenuHandle, + pub(crate) session_picker_menu_handle: PopoverMenuHandle, fs: Arc, } @@ -77,6 +79,8 @@ impl DebugPanel { cx.new(|cx| { let project = workspace.project().clone(); let focus_handle = cx.focus_handle(); + let thread_picker_menu_handle = PopoverMenuHandle::default(); + let session_picker_menu_handle = PopoverMenuHandle::default(); let debug_panel = Self { size: px(300.), @@ -87,6 +91,8 @@ impl DebugPanel { workspace: workspace.weak_handle(), context_menu: None, fs: workspace.app_state().fs.clone(), + thread_picker_menu_handle, + session_picker_menu_handle, }; debug_panel @@ -1033,6 +1039,14 @@ impl DebugPanel { }) .unwrap_or_else(|err| Task::ready(Err(err))) } + + pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context) { + self.thread_picker_menu_handle.toggle(window, cx); + } + + pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context) { + self.session_picker_menu_handle.toggle(window, cx); + } } impl EventEmitter for DebugPanel {} @@ -1249,6 +1263,24 @@ impl Render for DebugPanel { .ok(); } }) + .on_action({ + let this = this.clone(); + move |_: &ToggleThreadPicker, window, cx| { + this.update(cx, |this, cx| { + this.toggle_thread_picker(window, cx); + }) + .ok(); + } + }) + .on_action({ + let this = this.clone(); + move |_: &ToggleSessionPicker, window, cx| { + this.update(cx, |this, cx| { + this.toggle_session_picker(window, cx); + }) + .ok(); + } + }) .when(self.active_session.is_some(), |this| { this.on_mouse_down( MouseButton::Right, diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 6df3390bf5..ef8621c33d 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -45,6 +45,8 @@ actions!( FocusLoadedSources, FocusTerminal, ShowStackTrace, + ToggleThreadPicker, + ToggleSessionPicker, ] ); diff --git a/crates/debugger_ui/src/dropdown_menus.rs b/crates/debugger_ui/src/dropdown_menus.rs index 7a6da979f4..cdcb70a016 100644 --- a/crates/debugger_ui/src/dropdown_menus.rs +++ b/crates/debugger_ui/src/dropdown_menus.rs @@ -132,7 +132,8 @@ impl DebugPanel { this }), ) - .style(DropdownStyle::Ghost), + .style(DropdownStyle::Ghost) + .handle(self.session_picker_menu_handle.clone()), ) } else { None @@ -163,7 +164,7 @@ impl DebugPanel { DropdownMenu::new_with_element( ("thread-list", session_id.0), trigger, - ContextMenu::build_eager(window, cx, move |mut this, _, _| { + ContextMenu::build(window, cx, move |mut this, _, _| { for (thread, _) in threads { let running_state = running_state.clone(); let thread_id = thread.id; @@ -177,7 +178,8 @@ impl DebugPanel { }), ) .disabled(session_terminated) - .style(DropdownStyle::Ghost), + .style(DropdownStyle::Ghost) + .handle(self.thread_picker_menu_handle.clone()), ) } else { None diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 9eed056a7b..f13364cc5c 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -96,7 +96,7 @@ impl Render for RunningState { .find(|pane| pane.read(cx).is_zoomed()); let active = self.panes.panes().into_iter().next(); - let x = if let Some(ref zoomed_pane) = zoomed_pane { + let pane = if let Some(ref zoomed_pane) = zoomed_pane { zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element()) } else if let Some(active) = active { self.panes @@ -122,7 +122,7 @@ impl Render for RunningState { .size_full() .key_context("DebugSessionItem") .track_focus(&self.focus_handle(cx)) - .child(h_flex().flex_1().child(x)) + .child(h_flex().flex_1().child(pane)) } } diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 814ce06777..91b2dc8fd4 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -154,7 +154,6 @@ pub struct ContextMenu { key_context: SharedString, _on_blur_subscription: Subscription, keep_open_on_confirm: bool, - eager: bool, documentation_aside: Option<(usize, DocumentationAside)>, fixed_width: Option, } @@ -207,7 +206,6 @@ impl ContextMenu { key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: false, - eager: false, documentation_aside: None, fixed_width: None, end_slot_action: None, @@ -250,43 +248,6 @@ impl ContextMenu { key_context: "menu".into(), _on_blur_subscription, keep_open_on_confirm: true, - eager: false, - documentation_aside: None, - fixed_width: None, - end_slot_action: None, - }, - window, - cx, - ) - }) - } - - pub fn build_eager( - window: &mut Window, - cx: &mut App, - f: impl FnOnce(Self, &mut Window, &mut Context) -> Self, - ) -> Entity { - cx.new(|cx| { - let focus_handle = cx.focus_handle(); - let _on_blur_subscription = cx.on_blur( - &focus_handle, - window, - |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx), - ); - window.refresh(); - f( - Self { - builder: None, - items: Default::default(), - focus_handle, - action_context: None, - selected_index: None, - delayed: false, - clicked: false, - key_context: "menu".into(), - _on_blur_subscription, - keep_open_on_confirm: false, - eager: true, documentation_aside: None, fixed_width: None, end_slot_action: None, @@ -327,7 +288,6 @@ impl ContextMenu { |this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx), ), keep_open_on_confirm: false, - eager: false, documentation_aside: None, fixed_width: None, end_slot_action: None, @@ -634,10 +594,7 @@ impl ContextMenu { .. }) | ContextMenuItem::CustomEntry { handler, .. }, - ) = self - .selected_index - .and_then(|ix| self.items.get(ix)) - .filter(|_| !self.eager) + ) = self.selected_index.and_then(|ix| self.items.get(ix)) { (handler)(context, window, cx) } @@ -740,10 +697,9 @@ impl ContextMenu { fn select_index( &mut self, ix: usize, - window: &mut Window, - cx: &mut Context, + _window: &mut Window, + _cx: &mut Context, ) -> Option { - let context = self.action_context.as_ref(); self.documentation_aside = None; let item = self.items.get(ix)?; if item.is_selectable() { @@ -752,9 +708,6 @@ impl ContextMenu { if let Some(callback) = &entry.documentation_aside { self.documentation_aside = Some((ix, callback.clone())); } - if self.eager && !entry.disabled { - (entry.handler)(context, window, cx) - } } } Some(ix) diff --git a/crates/ui/src/components/dropdown_menu.rs b/crates/ui/src/components/dropdown_menu.rs index 174f893b5b..189fac930f 100644 --- a/crates/ui/src/components/dropdown_menu.rs +++ b/crates/ui/src/components/dropdown_menu.rs @@ -2,6 +2,8 @@ use gpui::{ClickEvent, Corner, CursorStyle, Entity, Hsla, MouseButton}; use crate::{ContextMenu, PopoverMenu, prelude::*}; +use super::PopoverMenuHandle; + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum DropdownStyle { #[default] @@ -22,6 +24,7 @@ pub struct DropdownMenu { menu: Entity, full_width: bool, disabled: bool, + handle: Option>, } impl DropdownMenu { @@ -37,6 +40,7 @@ impl DropdownMenu { menu, full_width: false, disabled: false, + handle: None, } } @@ -52,6 +56,7 @@ impl DropdownMenu { menu, full_width: false, disabled: false, + handle: None, } } @@ -64,6 +69,11 @@ impl DropdownMenu { self.full_width = full_width; self } + + pub fn handle(mut self, handle: PopoverMenuHandle) -> Self { + self.handle = Some(handle); + self + } } impl Disableable for DropdownMenu { @@ -85,6 +95,7 @@ impl RenderOnce for DropdownMenu { .style(self.style), ) .attach(Corner::BottomLeft) + .when_some(self.handle.clone(), |el, handle| el.with_handle(handle)) } } @@ -159,17 +170,11 @@ pub struct DropdownTriggerStyle { impl DropdownTriggerStyle { pub fn for_style(style: DropdownStyle, cx: &App) -> Self { let colors = cx.theme().colors(); - - if style == DropdownStyle::Solid { - Self { - // why is this editor_background? - bg: colors.editor_background, - } - } else { - Self { - bg: colors.ghost_element_background, - } - } + let bg = match style { + DropdownStyle::Solid => colors.editor_background, + DropdownStyle::Ghost => colors.ghost_element_background, + }; + Self { bg } } }