debugger_ui: Preview thread state when using the dropdown (#28778)

This PR changes the thread list dropdown menu in the debugger UI to
eagerly preview the state of a thread when selecting it, instead of
waiting until confirming the selection.

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-04-15 12:10:32 -04:00 committed by GitHub
parent 90dec1d451
commit 42c3f4e7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 104 additions and 47 deletions

View file

@ -768,7 +768,7 @@ impl RunningState {
DropdownMenu::new( DropdownMenu::new(
("thread-list", self.session_id.0), ("thread-list", self.session_id.0),
selected_thread_name, selected_thread_name,
ContextMenu::build(window, cx, move |mut this, _, _| { ContextMenu::build_eager(window, cx, move |mut this, _, _| {
for (thread, _) in threads { for (thread, _) in threads {
let state = state.clone(); let state = state.clone();
let thread_id = thread.id; let thread_id = thread.id;

View file

@ -1421,7 +1421,10 @@ impl Render for LspLogToolbarItemView {
}) })
})?; })?;
ContextMenu::build(window, cx, |mut menu, _, _| { ContextMenu::build(
window,
cx,
|mut menu, window, cx| {
let log_view = log_view.clone(); let log_view = log_view.clone();
for (option, label) in [ for (option, label) in [
@ -1444,12 +1447,13 @@ impl Render for LspLogToolbarItemView {
} }
}); });
if option == trace_level { if option == trace_level {
menu.select_last(); menu.select_last(window, cx);
} }
} }
menu menu
}) },
)
.into() .into()
} }
}), }),
@ -1480,7 +1484,10 @@ impl Render for LspLogToolbarItemView {
}) })
})?; })?;
ContextMenu::build(window, cx, |mut menu, _, _| { ContextMenu::build(
window,
cx,
|mut menu, window, cx| {
let log_view = log_view.clone(); let log_view = log_view.clone();
for (option, label) in [ for (option, label) in [
@ -1504,12 +1511,13 @@ impl Render for LspLogToolbarItemView {
} }
}); });
if option == log_level { if option == log_level {
menu.select_last(); menu.select_last(window, cx);
} }
} }
menu menu
}) },
)
.into() .into()
} }
}), }),

View file

@ -135,6 +135,7 @@ pub struct ContextMenu {
clicked: bool, clicked: bool,
_on_blur_subscription: Subscription, _on_blur_subscription: Subscription,
keep_open_on_confirm: bool, keep_open_on_confirm: bool,
eager: bool,
documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>, documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>,
} }
@ -173,6 +174,7 @@ impl ContextMenu {
clicked: false, clicked: false,
_on_blur_subscription, _on_blur_subscription,
keep_open_on_confirm: false, keep_open_on_confirm: false,
eager: false,
documentation_aside: None, documentation_aside: None,
}, },
window, window,
@ -212,6 +214,40 @@ impl ContextMenu {
clicked: false, clicked: false,
_on_blur_subscription, _on_blur_subscription,
keep_open_on_confirm: true, keep_open_on_confirm: true,
eager: false,
documentation_aside: 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,
_on_blur_subscription,
keep_open_on_confirm: false,
eager: true,
documentation_aside: None, documentation_aside: None,
}, },
window, window,
@ -249,6 +285,7 @@ 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,
}, },
window, window,
@ -435,7 +472,10 @@ impl ContextMenu {
.. ..
}) })
| ContextMenuItem::CustomEntry { handler, .. }, | ContextMenuItem::CustomEntry { handler, .. },
) = self.selected_index.and_then(|ix| self.items.get(ix)) ) = self
.selected_index
.and_then(|ix| self.items.get(ix))
.filter(|_| !self.eager)
{ {
(handler)(context, window, cx) (handler)(context, window, cx)
} }
@ -452,24 +492,24 @@ impl ContextMenu {
cx.emit(DismissEvent); cx.emit(DismissEvent);
} }
fn select_first(&mut self, _: &SelectFirst, _: &mut Window, cx: &mut Context<Self>) { fn select_first(&mut self, _: &SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) { if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) {
self.select_index(ix); self.select_index(ix, window, cx);
} }
cx.notify(); cx.notify();
} }
pub fn select_last(&mut self) -> Option<usize> { pub fn select_last(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<usize> {
for (ix, item) in self.items.iter().enumerate().rev() { for (ix, item) in self.items.iter().enumerate().rev() {
if item.is_selectable() { if item.is_selectable() {
return self.select_index(ix); return self.select_index(ix, window, cx);
} }
} }
None None
} }
fn handle_select_last(&mut self, _: &SelectLast, _: &mut Window, cx: &mut Context<Self>) { fn handle_select_last(&mut self, _: &SelectLast, window: &mut Window, cx: &mut Context<Self>) {
if self.select_last().is_some() { if self.select_last(window, cx).is_some() {
cx.notify(); cx.notify();
} }
} }
@ -482,7 +522,7 @@ impl ContextMenu {
} else { } else {
for (ix, item) in self.items.iter().enumerate().skip(next_index) { for (ix, item) in self.items.iter().enumerate().skip(next_index) {
if item.is_selectable() { if item.is_selectable() {
self.select_index(ix); self.select_index(ix, window, cx);
cx.notify(); cx.notify();
break; break;
} }
@ -505,7 +545,7 @@ impl ContextMenu {
} else { } else {
for (ix, item) in self.items.iter().enumerate().take(ix).rev() { for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
if item.is_selectable() { if item.is_selectable() {
self.select_index(ix); self.select_index(ix, window, cx);
cx.notify(); cx.notify();
break; break;
} }
@ -516,7 +556,13 @@ impl ContextMenu {
} }
} }
fn select_index(&mut self, ix: usize) -> Option<usize> { fn select_index(
&mut self,
ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) -> 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() {
@ -525,6 +571,9 @@ 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)
@ -553,7 +602,7 @@ impl ContextMenu {
false false
} }
}) { }) {
self.select_index(ix); self.select_index(ix, window, cx);
self.delayed = true; self.delayed = true;
cx.notify(); cx.notify();
let action = dispatched.boxed_clone(); let action = dispatched.boxed_clone();