debugger: Fix stack frame list flickering (#29282)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
Cole Miller 2025-04-23 12:12:53 -04:00 committed by GitHub
parent 19fb1e1b0d
commit 4b9f4feff1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 55 additions and 29 deletions

View file

@ -411,12 +411,12 @@ impl RunningState {
.log_err(); .log_err();
if let Some(thread_id) = thread_id { if let Some(thread_id) = thread_id {
this.select_thread(*thread_id, cx); this.select_thread(*thread_id, window, cx);
} }
} }
SessionEvent::Threads => { SessionEvent::Threads => {
let threads = this.session.update(cx, |this, cx| this.threads(cx)); let threads = this.session.update(cx, |this, cx| this.threads(cx));
this.select_current_thread(&threads, cx); this.select_current_thread(&threads, window, cx);
} }
SessionEvent::CapabilitiesLoaded => { SessionEvent::CapabilitiesLoaded => {
let capabilities = this.capabilities(cx); let capabilities = this.capabilities(cx);
@ -731,6 +731,7 @@ impl RunningState {
pub fn select_current_thread( pub fn select_current_thread(
&mut self, &mut self,
threads: &Vec<(Thread, ThreadStatus)>, threads: &Vec<(Thread, ThreadStatus)>,
window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let selected_thread = self let selected_thread = self
@ -743,7 +744,7 @@ impl RunningState {
}; };
if Some(ThreadId(selected_thread.id)) != self.thread_id { if Some(ThreadId(selected_thread.id)) != self.thread_id {
self.select_thread(ThreadId(selected_thread.id), cx); self.select_thread(ThreadId(selected_thread.id), window, cx);
} }
} }
@ -756,7 +757,7 @@ impl RunningState {
.map(|id| self.session().read(cx).thread_status(id)) .map(|id| self.session().read(cx).thread_status(id))
} }
fn select_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) { fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
if self.thread_id.is_some_and(|id| id == thread_id) { if self.thread_id.is_some_and(|id| id == thread_id) {
return; return;
} }
@ -764,8 +765,7 @@ impl RunningState {
self.thread_id = Some(thread_id); self.thread_id = Some(thread_id);
self.stack_frame_list self.stack_frame_list
.update(cx, |list, cx| list.refresh(cx)); .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
cx.notify();
} }
pub fn continue_thread(&mut self, cx: &mut Context<Self>) { pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
@ -917,9 +917,9 @@ impl RunningState {
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;
this = this.entry(thread.name, None, move |_, cx| { this = this.entry(thread.name, None, move |window, cx| {
state.update(cx, |state, cx| { state.update(cx, |state, cx| {
state.select_thread(ThreadId(thread_id), cx); state.select_thread(ThreadId(thread_id), window, cx);
}); });
}); });
} }

View file

@ -1,5 +1,6 @@
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use dap::StackFrameId; use dap::StackFrameId;
@ -28,11 +29,11 @@ pub struct StackFrameList {
_subscription: Subscription, _subscription: Subscription,
session: Entity<Session>, session: Entity<Session>,
state: WeakEntity<RunningState>, state: WeakEntity<RunningState>,
invalidate: bool,
entries: Vec<StackFrameEntry>, entries: Vec<StackFrameEntry>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
selected_stack_frame_id: Option<StackFrameId>, selected_stack_frame_id: Option<StackFrameId>,
scrollbar_state: ScrollbarState, scrollbar_state: ScrollbarState,
_refresh_task: Task<()>,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -68,14 +69,17 @@ impl StackFrameList {
); );
let _subscription = let _subscription =
cx.subscribe_in(&session, window, |this, _, event, _, cx| match event { cx.subscribe_in(&session, window, |this, _, event, window, cx| match event {
SessionEvent::Stopped(_) | SessionEvent::StackTrace | SessionEvent::Threads => { SessionEvent::Threads => {
this.refresh(cx); this.schedule_refresh(false, window, cx);
}
SessionEvent::Stopped(..) | SessionEvent::StackTrace => {
this.schedule_refresh(true, window, cx);
} }
_ => {} _ => {}
}); });
Self { let mut this = Self {
scrollbar_state: ScrollbarState::new(list.clone()), scrollbar_state: ScrollbarState::new(list.clone()),
list, list,
session, session,
@ -83,10 +87,12 @@ impl StackFrameList {
focus_handle, focus_handle,
state, state,
_subscription, _subscription,
invalidate: true,
entries: Default::default(), entries: Default::default(),
selected_stack_frame_id: None, selected_stack_frame_id: None,
} _refresh_task: Task::ready(()),
};
this.schedule_refresh(true, window, cx);
this
} }
#[cfg(test)] #[cfg(test)]
@ -136,10 +142,32 @@ impl StackFrameList {
self.selected_stack_frame_id self.selected_stack_frame_id
} }
pub(super) fn refresh(&mut self, cx: &mut Context<Self>) { pub(super) fn schedule_refresh(
self.invalidate = true; &mut self,
self.entries.clear(); select_first: bool,
cx.notify(); window: &mut Window,
cx: &mut Context<Self>,
) {
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(20);
self._refresh_task = cx.spawn_in(window, async move |this, cx| {
let debounce = this
.update(cx, |this, cx| {
let new_stack_frames = this.stack_frames(cx);
new_stack_frames.is_empty() && !this.entries.is_empty()
})
.ok()
.unwrap_or_default();
if debounce {
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
}
this.update_in(cx, |this, window, cx| {
this.build_entries(select_first, window, cx);
cx.notify();
})
.ok();
})
} }
pub fn build_entries( pub fn build_entries(
@ -515,13 +543,7 @@ impl StackFrameList {
} }
impl Render for StackFrameList { impl Render for StackFrameList {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.invalidate {
self.build_entries(self.entries.is_empty(), window, cx);
self.invalidate = false;
cx.notify();
}
div() div()
.size_full() .size_full()
.p_1() .p_1()

View file

@ -152,7 +152,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
cx.run_until_parked(); cx.run_until_parked();
// select first thread // select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| { active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
session session
.mode() .mode()
.as_running() .as_running()
@ -162,6 +162,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
&running_state &running_state
.session() .session()
.update(cx, |session, cx| session.threads(cx)), .update(cx, |session, cx| session.threads(cx)),
window,
cx, cx,
); );
}); });
@ -330,7 +331,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
cx.run_until_parked(); cx.run_until_parked();
// select first thread // select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| { active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
session session
.mode() .mode()
.as_running() .as_running()
@ -340,6 +341,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
&running_state &running_state
.session() .session()
.update(cx, |session, cx| session.threads(cx)), .update(cx, |session, cx| session.threads(cx)),
window,
cx, cx,
); );
}); });
@ -704,7 +706,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
cx.run_until_parked(); cx.run_until_parked();
// select first thread // select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| { active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
session session
.mode() .mode()
.as_running() .as_running()
@ -714,6 +716,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
&running_state &running_state
.session() .session()
.update(cx, |session, cx| session.threads(cx)), .update(cx, |session, cx| session.threads(cx)),
window,
cx, cx,
); );
}); });

View file

@ -641,6 +641,7 @@ impl CompletionsQuery {
} }
} }
#[derive(Debug)]
pub enum SessionEvent { pub enum SessionEvent {
Modules, Modules,
LoadedSources, LoadedSources,