From 4b9f4feff1d74247a6cbc2b5c809c85765759e62 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 23 Apr 2025 12:12:53 -0400 Subject: [PATCH] debugger: Fix stack frame list flickering (#29282) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Anthony Eid --- crates/debugger_ui/src/session/running.rs | 16 ++--- .../src/session/running/stack_frame_list.rs | 58 +++++++++++++------ .../debugger_ui/src/tests/stack_frame_list.rs | 9 ++- crates/project/src/debugger/session.rs | 1 + 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 438ded2e36..85c2c85f89 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -411,12 +411,12 @@ impl RunningState { .log_err(); if let Some(thread_id) = thread_id { - this.select_thread(*thread_id, cx); + this.select_thread(*thread_id, window, cx); } } SessionEvent::Threads => { 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 => { let capabilities = this.capabilities(cx); @@ -731,6 +731,7 @@ impl RunningState { pub fn select_current_thread( &mut self, threads: &Vec<(Thread, ThreadStatus)>, + window: &mut Window, cx: &mut Context, ) { let selected_thread = self @@ -743,7 +744,7 @@ impl RunningState { }; 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)) } - fn select_thread(&mut self, thread_id: ThreadId, cx: &mut Context) { + fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context) { if self.thread_id.is_some_and(|id| id == thread_id) { return; } @@ -764,8 +765,7 @@ impl RunningState { self.thread_id = Some(thread_id); self.stack_frame_list - .update(cx, |list, cx| list.refresh(cx)); - cx.notify(); + .update(cx, |list, cx| list.schedule_refresh(true, window, cx)); } pub fn continue_thread(&mut self, cx: &mut Context) { @@ -917,9 +917,9 @@ impl RunningState { for (thread, _) in threads { let state = state.clone(); 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.select_thread(ThreadId(thread_id), cx); + state.select_thread(ThreadId(thread_id), window, cx); }); }); } 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 bbb6e1f684..c9e7a9b11c 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -1,5 +1,6 @@ use std::path::Path; use std::sync::Arc; +use std::time::Duration; use anyhow::{Result, anyhow}; use dap::StackFrameId; @@ -28,11 +29,11 @@ pub struct StackFrameList { _subscription: Subscription, session: Entity, state: WeakEntity, - invalidate: bool, entries: Vec, workspace: WeakEntity, selected_stack_frame_id: Option, scrollbar_state: ScrollbarState, + _refresh_task: Task<()>, } #[allow(clippy::large_enum_variant)] @@ -68,14 +69,17 @@ impl StackFrameList { ); let _subscription = - cx.subscribe_in(&session, window, |this, _, event, _, cx| match event { - SessionEvent::Stopped(_) | SessionEvent::StackTrace | SessionEvent::Threads => { - this.refresh(cx); + cx.subscribe_in(&session, window, |this, _, event, window, cx| match event { + SessionEvent::Threads => { + 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()), list, session, @@ -83,10 +87,12 @@ impl StackFrameList { focus_handle, state, _subscription, - invalidate: true, entries: Default::default(), selected_stack_frame_id: None, - } + _refresh_task: Task::ready(()), + }; + this.schedule_refresh(true, window, cx); + this } #[cfg(test)] @@ -136,10 +142,32 @@ impl StackFrameList { self.selected_stack_frame_id } - pub(super) fn refresh(&mut self, cx: &mut Context) { - self.invalidate = true; - self.entries.clear(); - cx.notify(); + pub(super) fn schedule_refresh( + &mut self, + select_first: bool, + window: &mut Window, + cx: &mut Context, + ) { + 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( @@ -515,13 +543,7 @@ impl StackFrameList { } impl Render for StackFrameList { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - if self.invalidate { - self.build_entries(self.entries.is_empty(), window, cx); - self.invalidate = false; - cx.notify(); - } - + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { div() .size_full() .p_1() diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index e6f0a4478f..44284e220d 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -152,7 +152,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( cx.run_until_parked(); // 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 .mode() .as_running() @@ -162,6 +162,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( &running_state .session() .update(cx, |session, cx| session.threads(cx)), + window, cx, ); }); @@ -330,7 +331,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC cx.run_until_parked(); // 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 .mode() .as_running() @@ -340,6 +341,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC &running_state .session() .update(cx, |session, cx| session.threads(cx)), + window, cx, ); }); @@ -704,7 +706,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo cx.run_until_parked(); // 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 .mode() .as_running() @@ -714,6 +716,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo &running_state .session() .update(cx, |session, cx| session.threads(cx)), + window, cx, ); }); diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index bfa51335af..6a147bbb4e 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -641,6 +641,7 @@ impl CompletionsQuery { } } +#[derive(Debug)] pub enum SessionEvent { Modules, LoadedSources,