acp: Fix handling of single-file worktrees (#37412)

Cole Miller created

When the first visible worktree is a single-file worktree, we would
previously try to use the absolute path of that file as the root
directory for external agents, causing an error. This PR changes how we
handle this situation: we'll use the root of the first non-single-file
visible worktree if there are any, and if there are none, the parent
directory of the first single-file visible worktree.

Related to #37213

Release Notes:

- acp: Fixed being unable to run external agents when a single file (not
part of a project) was opened in Zed.

Change summary

crates/agent_ui/src/acp/thread_view.rs | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)

Detailed changes

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -427,11 +427,24 @@ impl AcpThreadView {
                 "External agents are not yet supported for remote projects.".into(),
             ));
         }
-        let root_dir = project
-            .read(cx)
-            .visible_worktrees(cx)
+        let mut worktrees = project.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
+        // Pick the first non-single-file worktree for the root directory if there are any,
+        // and otherwise the parent of a single-file worktree, falling back to $HOME if there are no visible worktrees.
+        worktrees.sort_by(|l, r| {
+            l.read(cx)
+                .is_single_file()
+                .cmp(&r.read(cx).is_single_file())
+        });
+        let root_dir = worktrees
+            .into_iter()
+            .filter_map(|worktree| {
+                if worktree.read(cx).is_single_file() {
+                    Some(worktree.read(cx).abs_path().parent()?.into())
+                } else {
+                    Some(worktree.read(cx).abs_path())
+                }
+            })
             .next()
-            .map(|worktree| worktree.read(cx).abs_path())
             .unwrap_or_else(|| paths::home_dir().as_path().into());
         let (tx, mut rx) = watch::channel("Loading…".into());
         let delegate = AgentServerDelegate::new(project.clone(), Some(tx));