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.
This commit is contained in:
parent
eb35d25a7d
commit
b2a92097ee
8 changed files with 76 additions and 68 deletions
|
@ -862,6 +862,13 @@
|
||||||
"alt-l": "git::GenerateCommitMessage"
|
"alt-l": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "DebugPanel",
|
||||||
|
"bindings": {
|
||||||
|
"ctrl-t": "debugger::ToggleThreadPicker",
|
||||||
|
"ctrl-i": "debugger::ToggleSessionPicker"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -929,6 +929,13 @@
|
||||||
"alt-tab": "git::GenerateCommitMessage"
|
"alt-tab": "git::GenerateCommitMessage"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "DebugPanel",
|
||||||
|
"bindings": {
|
||||||
|
"cmd-t": "debugger::ToggleThreadPicker",
|
||||||
|
"cmd-i": "debugger::ToggleSessionPicker"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "CollabPanel && not_editing",
|
"context": "CollabPanel && not_editing",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
|
||||||
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
|
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
|
||||||
ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
|
||||||
persistence,
|
ToggleSessionPicker, ToggleThreadPicker, persistence,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
|
@ -31,7 +31,7 @@ use settings::Settings;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use task::{DebugScenario, TaskContext};
|
use task::{DebugScenario, TaskContext};
|
||||||
use ui::{ContextMenu, Divider, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
Pane, Workspace,
|
Pane, Workspace,
|
||||||
|
@ -65,6 +65,8 @@ pub struct DebugPanel {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
|
pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
|
pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +79,8 @@ impl DebugPanel {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
let thread_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
|
let session_picker_menu_handle = PopoverMenuHandle::default();
|
||||||
|
|
||||||
let debug_panel = Self {
|
let debug_panel = Self {
|
||||||
size: px(300.),
|
size: px(300.),
|
||||||
|
@ -87,6 +91,8 @@ impl DebugPanel {
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
|
thread_picker_menu_handle,
|
||||||
|
session_picker_menu_handle,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_panel
|
debug_panel
|
||||||
|
@ -1033,6 +1039,14 @@ impl DebugPanel {
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|err| Task::ready(Err(err)))
|
.unwrap_or_else(|err| Task::ready(Err(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.thread_picker_menu_handle.toggle(window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.session_picker_menu_handle.toggle(window, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<PanelEvent> for DebugPanel {}
|
impl EventEmitter<PanelEvent> for DebugPanel {}
|
||||||
|
@ -1249,6 +1263,24 @@ impl Render for DebugPanel {
|
||||||
.ok();
|
.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| {
|
.when(self.active_session.is_some(), |this| {
|
||||||
this.on_mouse_down(
|
this.on_mouse_down(
|
||||||
MouseButton::Right,
|
MouseButton::Right,
|
||||||
|
|
|
@ -45,6 +45,8 @@ actions!(
|
||||||
FocusLoadedSources,
|
FocusLoadedSources,
|
||||||
FocusTerminal,
|
FocusTerminal,
|
||||||
ShowStackTrace,
|
ShowStackTrace,
|
||||||
|
ToggleThreadPicker,
|
||||||
|
ToggleSessionPicker,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,8 @@ impl DebugPanel {
|
||||||
this
|
this
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.style(DropdownStyle::Ghost),
|
.style(DropdownStyle::Ghost)
|
||||||
|
.handle(self.session_picker_menu_handle.clone()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -163,7 +164,7 @@ impl DebugPanel {
|
||||||
DropdownMenu::new_with_element(
|
DropdownMenu::new_with_element(
|
||||||
("thread-list", session_id.0),
|
("thread-list", session_id.0),
|
||||||
trigger,
|
trigger,
|
||||||
ContextMenu::build_eager(window, cx, move |mut this, _, _| {
|
ContextMenu::build(window, cx, move |mut this, _, _| {
|
||||||
for (thread, _) in threads {
|
for (thread, _) in threads {
|
||||||
let running_state = running_state.clone();
|
let running_state = running_state.clone();
|
||||||
let thread_id = thread.id;
|
let thread_id = thread.id;
|
||||||
|
@ -177,7 +178,8 @@ impl DebugPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.disabled(session_terminated)
|
.disabled(session_terminated)
|
||||||
.style(DropdownStyle::Ghost),
|
.style(DropdownStyle::Ghost)
|
||||||
|
.handle(self.thread_picker_menu_handle.clone()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl Render for RunningState {
|
||||||
.find(|pane| pane.read(cx).is_zoomed());
|
.find(|pane| pane.read(cx).is_zoomed());
|
||||||
|
|
||||||
let active = self.panes.panes().into_iter().next();
|
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())
|
zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())
|
||||||
} else if let Some(active) = active {
|
} else if let Some(active) = active {
|
||||||
self.panes
|
self.panes
|
||||||
|
@ -122,7 +122,7 @@ impl Render for RunningState {
|
||||||
.size_full()
|
.size_full()
|
||||||
.key_context("DebugSessionItem")
|
.key_context("DebugSessionItem")
|
||||||
.track_focus(&self.focus_handle(cx))
|
.track_focus(&self.focus_handle(cx))
|
||||||
.child(h_flex().flex_1().child(x))
|
.child(h_flex().flex_1().child(pane))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -154,7 +154,6 @@ pub struct ContextMenu {
|
||||||
key_context: SharedString,
|
key_context: SharedString,
|
||||||
_on_blur_subscription: Subscription,
|
_on_blur_subscription: Subscription,
|
||||||
keep_open_on_confirm: bool,
|
keep_open_on_confirm: bool,
|
||||||
eager: bool,
|
|
||||||
documentation_aside: Option<(usize, DocumentationAside)>,
|
documentation_aside: Option<(usize, DocumentationAside)>,
|
||||||
fixed_width: Option<DefiniteLength>,
|
fixed_width: Option<DefiniteLength>,
|
||||||
}
|
}
|
||||||
|
@ -207,7 +206,6 @@ impl ContextMenu {
|
||||||
key_context: "menu".into(),
|
key_context: "menu".into(),
|
||||||
_on_blur_subscription,
|
_on_blur_subscription,
|
||||||
keep_open_on_confirm: false,
|
keep_open_on_confirm: false,
|
||||||
eager: false,
|
|
||||||
documentation_aside: None,
|
documentation_aside: None,
|
||||||
fixed_width: None,
|
fixed_width: None,
|
||||||
end_slot_action: None,
|
end_slot_action: None,
|
||||||
|
@ -250,43 +248,6 @@ impl ContextMenu {
|
||||||
key_context: "menu".into(),
|
key_context: "menu".into(),
|
||||||
_on_blur_subscription,
|
_on_blur_subscription,
|
||||||
keep_open_on_confirm: true,
|
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>) -> Self,
|
|
||||||
) -> Entity<Self> {
|
|
||||||
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,
|
documentation_aside: None,
|
||||||
fixed_width: None,
|
fixed_width: None,
|
||||||
end_slot_action: None,
|
end_slot_action: None,
|
||||||
|
@ -327,7 +288,6 @@ impl ContextMenu {
|
||||||
|this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx),
|
|this: &mut ContextMenu, window, cx| this.cancel(&menu::Cancel, window, cx),
|
||||||
),
|
),
|
||||||
keep_open_on_confirm: false,
|
keep_open_on_confirm: false,
|
||||||
eager: false,
|
|
||||||
documentation_aside: None,
|
documentation_aside: None,
|
||||||
fixed_width: None,
|
fixed_width: None,
|
||||||
end_slot_action: None,
|
end_slot_action: None,
|
||||||
|
@ -634,10 +594,7 @@ impl ContextMenu {
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
| ContextMenuItem::CustomEntry { handler, .. },
|
| ContextMenuItem::CustomEntry { handler, .. },
|
||||||
) = self
|
) = self.selected_index.and_then(|ix| self.items.get(ix))
|
||||||
.selected_index
|
|
||||||
.and_then(|ix| self.items.get(ix))
|
|
||||||
.filter(|_| !self.eager)
|
|
||||||
{
|
{
|
||||||
(handler)(context, window, cx)
|
(handler)(context, window, cx)
|
||||||
}
|
}
|
||||||
|
@ -740,10 +697,9 @@ impl ContextMenu {
|
||||||
fn select_index(
|
fn select_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
_cx: &mut Context<Self>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
let context = self.action_context.as_ref();
|
|
||||||
self.documentation_aside = None;
|
self.documentation_aside = None;
|
||||||
let item = self.items.get(ix)?;
|
let item = self.items.get(ix)?;
|
||||||
if item.is_selectable() {
|
if item.is_selectable() {
|
||||||
|
@ -752,9 +708,6 @@ impl ContextMenu {
|
||||||
if let Some(callback) = &entry.documentation_aside {
|
if let Some(callback) = &entry.documentation_aside {
|
||||||
self.documentation_aside = Some((ix, callback.clone()));
|
self.documentation_aside = Some((ix, callback.clone()));
|
||||||
}
|
}
|
||||||
if self.eager && !entry.disabled {
|
|
||||||
(entry.handler)(context, window, cx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ix)
|
Some(ix)
|
||||||
|
|
|
@ -2,6 +2,8 @@ use gpui::{ClickEvent, Corner, CursorStyle, Entity, Hsla, MouseButton};
|
||||||
|
|
||||||
use crate::{ContextMenu, PopoverMenu, prelude::*};
|
use crate::{ContextMenu, PopoverMenu, prelude::*};
|
||||||
|
|
||||||
|
use super::PopoverMenuHandle;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum DropdownStyle {
|
pub enum DropdownStyle {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -22,6 +24,7 @@ pub struct DropdownMenu {
|
||||||
menu: Entity<ContextMenu>,
|
menu: Entity<ContextMenu>,
|
||||||
full_width: bool,
|
full_width: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
|
handle: Option<PopoverMenuHandle<ContextMenu>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DropdownMenu {
|
impl DropdownMenu {
|
||||||
|
@ -37,6 +40,7 @@ impl DropdownMenu {
|
||||||
menu,
|
menu,
|
||||||
full_width: false,
|
full_width: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
handle: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +56,7 @@ impl DropdownMenu {
|
||||||
menu,
|
menu,
|
||||||
full_width: false,
|
full_width: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
handle: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +69,11 @@ impl DropdownMenu {
|
||||||
self.full_width = full_width;
|
self.full_width = full_width;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle(mut self, handle: PopoverMenuHandle<ContextMenu>) -> Self {
|
||||||
|
self.handle = Some(handle);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Disableable for DropdownMenu {
|
impl Disableable for DropdownMenu {
|
||||||
|
@ -85,6 +95,7 @@ impl RenderOnce for DropdownMenu {
|
||||||
.style(self.style),
|
.style(self.style),
|
||||||
)
|
)
|
||||||
.attach(Corner::BottomLeft)
|
.attach(Corner::BottomLeft)
|
||||||
|
.when_some(self.handle.clone(), |el, handle| el.with_handle(handle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,17 +170,11 @@ pub struct DropdownTriggerStyle {
|
||||||
impl DropdownTriggerStyle {
|
impl DropdownTriggerStyle {
|
||||||
pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
|
pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
|
||||||
let colors = cx.theme().colors();
|
let colors = cx.theme().colors();
|
||||||
|
let bg = match style {
|
||||||
if style == DropdownStyle::Solid {
|
DropdownStyle::Solid => colors.editor_background,
|
||||||
Self {
|
DropdownStyle::Ghost => colors.ghost_element_background,
|
||||||
// why is this editor_background?
|
};
|
||||||
bg: colors.editor_background,
|
Self { bg }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self {
|
|
||||||
bg: colors.ghost_element_background,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue