debugger: Focus child sessions if parent has never stopped (#32693)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- When debugging JavaScript, Zed will now preselect child sessions by
default.

Change summary

crates/debugger_ui/src/debugger_panel.rs       |  6 +++++-
crates/debugger_ui/src/tests/debugger_panel.rs | 12 +++++++-----
crates/project/src/debugger/session.rs         | 20 ++++++++++++++++++++
3 files changed, 32 insertions(+), 6 deletions(-)

Detailed changes

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -404,7 +404,11 @@ impl DebugPanel {
                 });
                 (session, task)
             })?;
-            Self::register_session(this, session, false, cx).await?;
+            // Focus child sessions if the parent has never emitted a stopped event;
+            // this improves our JavaScript experience, as it always spawns a "main" session that then spawns subsessions.
+            let parent_ever_stopped =
+                parent_session.update(cx, |this, _| this.has_ever_stopped())?;
+            Self::register_session(this, session, !parent_ever_stopped, cx).await?;
             task.await
         })
         .detach_and_log_err(cx);

crates/debugger_ui/src/tests/debugger_panel.rs 🔗

@@ -444,18 +444,20 @@ async fn test_handle_start_debugging_request(
         .update(cx, |workspace, _window, cx| {
             let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
 
-            // Active session does not change on spawn.
+            // Active session changes on spawn, as the parent has never stopped.
             let active_session = debug_panel
                 .read(cx)
                 .active_session()
                 .unwrap()
                 .read(cx)
                 .session(cx);
-
-            assert_eq!(active_session, sessions[0].read(cx).session(cx));
-            assert!(active_session.read(cx).parent_session().is_none());
-
             let current_sessions = debug_panel.read(cx).sessions();
+            assert_eq!(active_session, current_sessions[1].read(cx).session(cx));
+            assert_eq!(
+                active_session.read(cx).parent_session(),
+                Some(&current_sessions[0].read(cx).session(cx))
+            );
+
             assert_eq!(current_sessions.len(), 2);
             assert_eq!(current_sessions[0], sessions[0]);
 

crates/project/src/debugger/session.rs 🔗

@@ -137,6 +137,7 @@ pub struct RunningMode {
     worktree: WeakEntity<Worktree>,
     executor: BackgroundExecutor,
     is_started: bool,
+    has_ever_stopped: bool,
 }
 
 fn client_source(abs_path: &Path) -> dap::Source {
@@ -188,6 +189,7 @@ impl RunningMode {
             binary,
             executor: cx.background_executor().clone(),
             is_started: false,
+            has_ever_stopped: false,
         })
     }
 
@@ -508,6 +510,20 @@ impl Mode {
             ))),
         }
     }
+
+    /// Did this debug session stop at least once?
+    pub(crate) fn has_ever_stopped(&self) -> bool {
+        match self {
+            Mode::Building => false,
+            Mode::Running(running_mode) => running_mode.has_ever_stopped,
+        }
+    }
+
+    fn stopped(&mut self) {
+        if let Mode::Running(running) = self {
+            running.has_ever_stopped = true;
+        }
+    }
 }
 
 #[derive(Default)]
@@ -1237,6 +1253,7 @@ impl Session {
     }
 
     fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
+        self.mode.stopped();
         // todo(debugger): Find a clean way to get around the clone
         let breakpoint_store = self.breakpoint_store.clone();
         if let Some((local, path)) = self.as_running_mut().and_then(|local| {
@@ -1831,6 +1848,9 @@ impl Session {
         }
     }
 
+    pub fn has_ever_stopped(&self) -> bool {
+        self.mode.has_ever_stopped()
+    }
     pub fn step_over(
         &mut self,
         thread_id: ThreadId,