From feef68bec7389f14849148f962daf994b4424420 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:37:03 -0400 Subject: [PATCH] debugger: Add support for label presentation hints for stack frames (#32719) Release Notes: - debugger: Add support for `Label` stack frame kinds Co-authored-by: Remco Smits --- .../src/session/running/stack_frame_list.rs | 66 ++++++++++++++----- crates/debugger_ui/src/stack_trace_view.rs | 4 +- crates/debugger_ui/src/tests/variable_list.rs | 25 +++++-- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index f7b78a7ebb81c177399fd4d6ef41fe686c37b711..efea3a9937482ae3b980a8d32895ae32fd5ef1af 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -5,9 +5,10 @@ use std::time::Duration; use anyhow::{Context as _, Result, anyhow}; use dap::StackFrameId; use gpui::{ - AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, MouseButton, Stateful, - Subscription, Task, WeakEntity, list, + AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState, MouseButton, + Stateful, Subscription, Task, WeakEntity, list, }; +use util::debug_panic; use crate::StackTraceView; use language::PointUtf16; @@ -43,6 +44,8 @@ pub struct StackFrameList { #[derive(Debug, PartialEq, Eq)] pub enum StackFrameEntry { Normal(dap::StackFrame), + /// Used to indicate that the frame is artificial and is a visual label or separator + Label(dap::StackFrame), Collapsed(Vec), } @@ -99,18 +102,18 @@ impl StackFrameList { &self.entries } - pub(crate) fn flatten_entries(&self, show_collapsed: bool) -> Vec { + pub(crate) fn flatten_entries( + &self, + show_collapsed: bool, + show_labels: bool, + ) -> Vec { self.entries .iter() .flat_map(|frame| match frame { StackFrameEntry::Normal(frame) => vec![frame.clone()], - StackFrameEntry::Collapsed(frames) => { - if show_collapsed { - frames.clone() - } else { - vec![] - } - } + StackFrameEntry::Label(frame) if show_labels => vec![frame.clone()], + StackFrameEntry::Collapsed(frames) if show_collapsed => frames.clone(), + _ => vec![], }) .collect::>() } @@ -176,9 +179,7 @@ impl StackFrameList { .and_then(|ix| self.entries.get(ix)) .and_then(|entry| match entry { StackFrameEntry::Normal(stack_frame) => Some(stack_frame.id), - StackFrameEntry::Collapsed(stack_frames) => { - stack_frames.first().map(|stack_frame| stack_frame.id) - } + StackFrameEntry::Collapsed(_) | StackFrameEntry::Label(_) => None, }); let mut entries = Vec::new(); let mut collapsed_entries = Vec::new(); @@ -202,6 +203,9 @@ impl StackFrameList { Some(dap::StackFramePresentationHint::Deemphasize) => { collapsed_entries.push(stack_frame.dap.clone()); } + Some(dap::StackFramePresentationHint::Label) => { + entries.push(StackFrameEntry::Label(stack_frame.dap.clone())); + } _ => { let collapsed_entries = std::mem::take(&mut collapsed_entries); if !collapsed_entries.is_empty() { @@ -234,9 +238,7 @@ impl StackFrameList { } else if let Some(old_selected_frame_id) = old_selected_frame_id { let ix = self.entries.iter().position(|entry| match entry { StackFrameEntry::Normal(frame) => frame.id == old_selected_frame_id, - StackFrameEntry::Collapsed(frames) => { - frames.iter().any(|frame| frame.id == old_selected_frame_id) - } + StackFrameEntry::Collapsed(_) | StackFrameEntry::Label(_) => false, }); self.selected_ix = ix; } @@ -256,6 +258,7 @@ impl StackFrameList { .entries .iter() .flat_map(|entry| match entry { + StackFrameEntry::Label(stack_frame) => std::slice::from_ref(stack_frame), StackFrameEntry::Normal(stack_frame) => std::slice::from_ref(stack_frame), StackFrameEntry::Collapsed(stack_frames) => stack_frames.as_slice(), }) @@ -380,6 +383,33 @@ impl StackFrameList { }); } + fn render_label_entry( + &self, + stack_frame: &dap::StackFrame, + _cx: &mut Context, + ) -> AnyElement { + h_flex() + .rounded_md() + .justify_between() + .w_full() + .group("") + .id(("label-stack-frame", stack_frame.id)) + .p_1() + .on_any_mouse_down(|_, _, cx| { + cx.stop_propagation(); + }) + .child( + v_flex().justify_center().gap_0p5().child( + Label::new(stack_frame.name.clone()) + .size(LabelSize::Small) + .weight(FontWeight::BOLD) + .truncate() + .color(Color::Info), + ), + ) + .into_any() + } + fn render_normal_entry( &self, ix: usize, @@ -541,6 +571,7 @@ impl StackFrameList { fn render_entry(&self, ix: usize, cx: &mut Context) -> AnyElement { match &self.entries[ix] { + StackFrameEntry::Label(stack_frame) => self.render_label_entry(stack_frame, cx), StackFrameEntry::Normal(stack_frame) => self.render_normal_entry(ix, stack_frame, cx), StackFrameEntry::Collapsed(stack_frames) => { self.render_collapsed_entry(ix, stack_frames, cx) @@ -657,6 +688,9 @@ impl StackFrameList { self.go_to_stack_frame_inner(stack_frame, window, cx) .detach_and_log_err(cx) } + StackFrameEntry::Label(_) => { + debug_panic!("You should not be able to select a label stack frame") + } StackFrameEntry::Collapsed(_) => self.expand_collapsed_entry(ix), } cx.notify(); diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index f6087352dc22dc2540fb5bf89312d973eb4789e7..675522e99996b276b5f62eeb88297dfe7d592579 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -148,7 +148,7 @@ impl StackTraceView { let stack_frames = self .stack_frame_list - .read_with(cx, |list, _| list.flatten_entries(false)); + .read_with(cx, |list, _| list.flatten_entries(false, false)); let frames_to_open: Vec<_> = stack_frames .into_iter() @@ -237,7 +237,7 @@ impl StackTraceView { let stack_frames = self .stack_frame_list - .read_with(cx, |session, _| session.flatten_entries(false)); + .read_with(cx, |session, _| session.flatten_entries(false, false)); let active_idx = self .selected_stack_frame_id diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index ae8cfcdc560242e3b144bfd5acd64b6e3588102d..2ae601eb9059141535f7407ab828186fadf2acb2 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -191,7 +191,10 @@ async fn test_basic_fetch_initial_scope_and_variables( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(true), list.opened_stack_frame_id()) + ( + list.flatten_entries(true, true), + list.opened_stack_frame_id(), + ) }); assert_eq!(stack_frames, stack_frame_list); @@ -432,7 +435,10 @@ async fn test_fetch_variables_for_multiple_scopes( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(true), list.opened_stack_frame_id()) + ( + list.flatten_entries(true, true), + list.opened_stack_frame_id(), + ) }); assert_eq!(Some(1), stack_frame_id); @@ -1459,7 +1465,10 @@ async fn test_variable_list_only_sends_requests_when_rendering( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(true), list.opened_stack_frame_id()) + ( + list.flatten_entries(true, true), + list.opened_stack_frame_id(), + ) }); assert_eq!(Some(1), stack_frame_id); @@ -1741,7 +1750,10 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(true), list.opened_stack_frame_id()) + ( + list.flatten_entries(true, true), + list.opened_stack_frame_id(), + ) }); let variable_list = running_state.variable_list().read(cx); @@ -1796,7 +1808,10 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(true), list.opened_stack_frame_id()) + ( + list.flatten_entries(true, true), + list.opened_stack_frame_id(), + ) }); let variable_list = running_state.variable_list().read(cx);