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 <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid 2025-06-13 17:37:03 -04:00 committed by GitHub
parent 6650be8e0f
commit feef68bec7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 72 additions and 23 deletions

View file

@ -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<dap::StackFrame>),
}
@ -99,18 +102,18 @@ impl StackFrameList {
&self.entries
}
pub(crate) fn flatten_entries(&self, show_collapsed: bool) -> Vec<dap::StackFrame> {
pub(crate) fn flatten_entries(
&self,
show_collapsed: bool,
show_labels: bool,
) -> Vec<dap::StackFrame> {
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::<Vec<_>>()
}
@ -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<Self>,
) -> 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<Self>) -> 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();

View file

@ -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

View file

@ -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);