debugger: Fix DebugAdapterDelegate::worktree_root always using the first visible worktree (#32585)

Piotr Osiewicz created

Closes #32577

Release Notes:

- Fixed debugger malfunctioning when using ZED_WORKTREE_ROOT env
variable in multi-worktree workspaces.

Change summary

crates/debugger_ui/src/debugger_panel.rs | 15 ++++++++++
crates/project/src/debugger/dap_store.rs | 34 ++++++++++++++++++-------
crates/proto/proto/debugger.proto        |  1 
crates/tasks_ui/src/tasks_ui.rs          | 10 ++++---
4 files changed, 45 insertions(+), 15 deletions(-)

Detailed changes

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -179,6 +179,19 @@ impl DebugPanel {
                 cx,
             )
         });
+        let worktree = worktree_id.or_else(|| {
+            active_buffer
+                .as_ref()
+                .and_then(|buffer| buffer.read(cx).file())
+                .map(|f| f.worktree_id(cx))
+        });
+        let Some(worktree) = worktree
+            .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
+            .or_else(|| self.project.read(cx).visible_worktrees(cx).next())
+        else {
+            log::debug!("Could not find a worktree to spawn the debug session in");
+            return;
+        };
         self.debug_scenario_scheduled_last = true;
         if let Some(inventory) = self
             .project
@@ -213,7 +226,7 @@ impl DebugPanel {
                     .await?;
                 dap_store
                     .update(cx, |dap_store, cx| {
-                        dap_store.boot_session(session.clone(), definition, cx)
+                        dap_store.boot_session(session.clone(), definition, worktree, cx)
                     })?
                     .await
             }

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

@@ -180,15 +180,12 @@ impl DapStore {
         &mut self,
         definition: DebugTaskDefinition,
         session_id: SessionId,
+        worktree: &Entity<Worktree>,
         console: UnboundedSender<String>,
         cx: &mut Context<Self>,
     ) -> Task<Result<DebugAdapterBinary>> {
         match &self.mode {
             DapStoreMode::Local(_) => {
-                let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
-                else {
-                    return Task::ready(Err(anyhow!("Failed to find a worktree")));
-                };
                 let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
                     return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
                 };
@@ -229,6 +226,7 @@ impl DapStore {
                 let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
                     session_id: session_id.to_proto(),
                     project_id: ssh.upstream_project_id,
+                    worktree_id: worktree.read(cx).id().to_proto(),
                     definition: Some(definition.to_proto()),
                 });
                 let ssh_client = ssh.ssh_client.clone();
@@ -401,12 +399,9 @@ impl DapStore {
         &self,
         session: Entity<Session>,
         definition: DebugTaskDefinition,
+        worktree: Entity<Worktree>,
         cx: &mut Context<Self>,
     ) -> Task<Result<()>> {
-        let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
-            return Task::ready(Err(anyhow!("Failed to find a worktree")));
-        };
-
         let dap_store = cx.weak_entity();
         let console = session.update(cx, |session, cx| session.console_output(cx));
         let session_id = session.read(cx).session_id();
@@ -416,7 +411,13 @@ impl DapStore {
             async move |this, cx| {
                 let binary = this
                     .update(cx, |this, cx| {
-                        this.get_debug_adapter_binary(definition.clone(), session_id, console, cx)
+                        this.get_debug_adapter_binary(
+                            definition.clone(),
+                            session_id,
+                            &worktree,
+                            console,
+                            cx,
+                        )
                     })?
                     .await?;
                 session
@@ -780,9 +781,22 @@ impl DapStore {
         })
         .detach();
 
+        let worktree = this
+            .update(&mut cx, |this, cx| {
+                this.worktree_store
+                    .read(cx)
+                    .worktree_for_id(WorktreeId::from_proto(envelope.payload.worktree_id), cx)
+            })?
+            .context("Failed to find worktree with a given ID")?;
         let binary = this
             .update(&mut cx, |this, cx| {
-                this.get_debug_adapter_binary(definition, SessionId::from_proto(session_id), tx, cx)
+                this.get_debug_adapter_binary(
+                    definition,
+                    SessionId::from_proto(session_id),
+                    &worktree,
+                    tx,
+                    cx,
+                )
             })?
             .await?;
         Ok(binary.to_proto())

crates/proto/proto/debugger.proto 🔗

@@ -493,6 +493,7 @@ message GetDebugAdapterBinary {
     uint64 project_id = 1;
     uint64 session_id = 3;
     DebugTaskDefinition definition = 2;
+    uint64 worktree_id = 4;
 }
 
 message DebugAdapterBinary {

crates/tasks_ui/src/tasks_ui.rs 🔗

@@ -285,10 +285,12 @@ pub fn task_contexts(
                 .worktree_for_id(*worktree_id, cx)
                 .map_or(false, |worktree| is_visible_directory(&worktree, cx))
         })
-        .or(workspace
-            .visible_worktrees(cx)
-            .next()
-            .map(|tree| tree.read(cx).id()));
+        .or_else(|| {
+            workspace
+                .visible_worktrees(cx)
+                .next()
+                .map(|tree| tree.read(cx).id())
+        });
 
     let active_editor = active_item.and_then(|item| item.act_as::<Editor>(cx));