Detailed changes
@@ -7,7 +7,7 @@ use anyhow::Result;
use collections::HashSet;
use fs::Fs;
use gpui::{App, Entity, Task};
-use project::AgentId;
+use project::{AgentId, Project};
use prompt_store::PromptStore;
use settings::{LanguageModelSelection, Settings as _, update_settings_file};
@@ -37,6 +37,7 @@ impl AgentServer for NativeAgentServer {
fn connect(
&self,
_delegate: AgentServerDelegate,
+ _project: Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn acp_thread::AgentConnection>>> {
log::debug!("NativeAgentServer::connect");
@@ -166,6 +166,7 @@ impl AgentSessionList for AcpSessionList {
pub async fn connect(
agent_id: AgentId,
+ project: Entity<Project>,
display_name: SharedString,
command: AgentServerCommand,
default_mode: Option<acp::SessionModeId>,
@@ -175,6 +176,7 @@ pub async fn connect(
) -> Result<Rc<dyn AgentConnection>> {
let conn = AcpConnection::stdio(
agent_id,
+ project,
display_name,
command.clone(),
default_mode,
@@ -191,6 +193,7 @@ const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::ProtocolVersion::V1
impl AcpConnection {
pub async fn stdio(
agent_id: AgentId,
+ project: Entity<Project>,
display_name: SharedString,
command: AgentServerCommand,
default_mode: Option<acp::SessionModeId>,
@@ -203,6 +206,15 @@ impl AcpConnection {
let mut child =
builder.build_std_command(Some(command.path.display().to_string()), &command.args);
child.envs(command.env.iter().flatten());
+ if let Some(cwd) = project.update(cx, |project, cx| {
+ project
+ .default_path_list(cx)
+ .ordered_paths()
+ .next()
+ .cloned()
+ }) {
+ child.current_dir(cwd);
+ }
let mut child = Child::spawn(child, Stdio::piped(), Stdio::piped(), Stdio::piped())?;
let stdout = child.stdout.take().context("Failed to take stdout")?;
@@ -9,7 +9,7 @@ use collections::{HashMap, HashSet};
pub use custom::*;
use fs::Fs;
use http_client::read_no_proxy_from_env;
-use project::{AgentId, agent_server_store::AgentServerStore};
+use project::{AgentId, Project, agent_server_store::AgentServerStore};
use acp_thread::AgentConnection;
use anyhow::Result;
@@ -42,6 +42,7 @@ pub trait AgentServer: Send {
fn connect(
&self,
delegate: AgentServerDelegate,
+ project: Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>>;
@@ -5,9 +5,12 @@ use anyhow::{Context as _, Result};
use collections::HashSet;
use credentials_provider::CredentialsProvider;
use fs::Fs;
-use gpui::{App, AppContext as _, Task};
+use gpui::{App, AppContext as _, Entity, Task};
use language_model::{ApiKey, EnvVar};
-use project::agent_server_store::{AgentId, AllAgentServersSettings};
+use project::{
+ Project,
+ agent_server_store::{AgentId, AllAgentServersSettings},
+};
use settings::{SettingsStore, update_settings_file};
use std::{rc::Rc, sync::Arc};
use ui::IconName;
@@ -289,6 +292,7 @@ impl AgentServer for CustomAgentServer {
fn connect(
&self,
delegate: AgentServerDelegate,
+ project: Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let agent_id = self.agent_id();
@@ -371,6 +375,7 @@ impl AgentServer for CustomAgentServer {
.await?;
let connection = crate::acp::connect(
agent_id,
+ project,
display_name,
command,
default_mode,
@@ -434,7 +434,10 @@ pub async fn new_test_thread(
let store = project.read_with(cx, |project, _| project.agent_server_store().clone());
let delegate = AgentServerDelegate::new(store, None);
- let connection = cx.update(|cx| server.connect(delegate, cx)).await.unwrap();
+ let connection = cx
+ .update(|cx| server.connect(delegate, project.clone(), cx))
+ .await
+ .unwrap();
cx.update(|cx| {
connection.new_session(project.clone(), PathList::new(&[current_dir.as_ref()]), cx)
@@ -160,7 +160,7 @@ impl AgentConnectionStore {
let agent_server_store = self.project.read(cx).agent_server_store().clone();
let delegate = AgentServerDelegate::new(agent_server_store, Some(new_version_tx));
- let connect_task = server.connect(delegate, cx);
+ let connect_task = server.connect(delegate, self.project.clone(), cx);
let connect_task = cx.spawn(async move |_this, cx| match connect_task.await {
Ok(connection) => cx.update(|cx| {
let history = connection
@@ -619,32 +619,7 @@ impl ConversationView {
session_id: resume_session_id.clone(),
};
}
- 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 worktree_roots: Vec<Arc<Path>> = worktrees
- .iter()
- .filter_map(|worktree| {
- let worktree = worktree.read(cx);
- if worktree.is_single_file() {
- Some(worktree.abs_path().parent()?.into())
- } else {
- Some(worktree.abs_path())
- }
- })
- .collect();
- let session_work_dirs = work_dirs.unwrap_or_else(|| {
- if worktree_roots.is_empty() {
- PathList::new(&[paths::home_dir().as_path()])
- } else {
- PathList::new(&worktree_roots)
- }
- });
+ let session_work_dirs = work_dirs.unwrap_or_else(|| project.read(cx).default_path_list(cx));
let connection_entry = connection_store.update(cx, |store, cx| {
store.request_connection(connection_key, agent.clone(), cx)
@@ -1624,7 +1599,7 @@ impl ConversationView {
.read(cx)
.work_dirs()
.cloned()
- .unwrap_or_else(|| PathList::new(&[paths::home_dir().as_path()]));
+ .unwrap_or_else(|| self.project.read(cx).default_path_list(cx));
let subagent_thread_task = connected.connection.clone().load_session(
subagent_id.clone(),
@@ -3651,6 +3626,7 @@ pub(crate) mod tests {
fn connect(
&self,
_delegate: AgentServerDelegate,
+ _project: Entity<Project>,
_cx: &mut App,
) -> Task<gpui::Result<Rc<dyn AgentConnection>>> {
Task::ready(Ok(Rc::new(self.connection.clone())))
@@ -3675,6 +3651,7 @@ pub(crate) mod tests {
fn connect(
&self,
_delegate: AgentServerDelegate,
+ _project: Entity<Project>,
_cx: &mut App,
) -> Task<gpui::Result<Rc<dyn AgentConnection>>> {
Task::ready(Err(anyhow!(
@@ -562,7 +562,7 @@ impl MentionSet {
));
let delegate =
AgentServerDelegate::new(project.read(cx).agent_server_store().clone(), None);
- let connection = server.connect(delegate, cx);
+ let connection = server.connect(delegate, project.clone(), cx);
cx.spawn(async move |_, cx| {
let agent = connection.await?;
let agent = agent.downcast::<agent::NativeAgentConnection>().unwrap();
@@ -3,6 +3,7 @@ use agent_client_protocol as acp;
use agent_servers::{AgentServer, AgentServerDelegate};
use gpui::{Entity, Task, TestAppContext, VisualTestContext};
use project::AgentId;
+use project::Project;
use settings::SettingsStore;
use std::any::Any;
use std::rc::Rc;
@@ -45,6 +46,7 @@ where
fn connect(
&self,
_delegate: AgentServerDelegate,
+ _project: Entity<Project>,
_cx: &mut gpui::App,
) -> Task<gpui::Result<Rc<dyn AgentConnection>>> {
Task::ready(Ok(Rc::new(self.connection.clone())))
@@ -33,7 +33,7 @@ pub mod search_history;
pub mod yarn;
use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope};
-use itertools::Either;
+use itertools::{Either, Itertools};
use crate::{
git_store::GitStore,
@@ -134,6 +134,7 @@ use text::{Anchor, BufferId, OffsetRangeExt, Point, Rope};
use toolchain_store::EmptyToolchainStore;
use util::{
ResultExt as _, maybe,
+ path_list::PathList,
paths::{PathStyle, SanitizedPath, is_absolute},
rel_path::RelPath,
};
@@ -2286,6 +2287,32 @@ impl Project {
self.worktree_store.read(cx).visible_worktrees(cx)
}
+ pub fn default_path_list(&self, cx: &App) -> PathList {
+ let worktree_roots = self
+ .visible_worktrees(cx)
+ .sorted_by(|left, right| {
+ left.read(cx)
+ .is_single_file()
+ .cmp(&right.read(cx).is_single_file())
+ })
+ .filter_map(|worktree| {
+ let worktree = worktree.read(cx);
+ let path = worktree.abs_path();
+ if worktree.is_single_file() {
+ Some(path.parent()?.to_path_buf())
+ } else {
+ Some(path.to_path_buf())
+ }
+ })
+ .collect::<Vec<_>>();
+
+ if worktree_roots.is_empty() {
+ PathList::new(&[paths::home_dir().as_path()])
+ } else {
+ PathList::new(&worktree_roots)
+ }
+ }
+
#[inline]
pub fn worktree_for_root_name(&self, root_name: &str, cx: &App) -> Option<Entity<Worktree>> {
self.visible_worktrees(cx)
@@ -126,6 +126,63 @@ async fn test_block_via_smol(cx: &mut gpui::TestAppContext) {
task.await;
}
+#[gpui::test]
+async fn test_default_session_work_dirs_prefers_directory_worktrees_over_single_file_parents(
+ cx: &mut gpui::TestAppContext,
+) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/root"),
+ json!({
+ "dir-project": {
+ "src": {
+ "main.rs": "fn main() {}"
+ }
+ },
+ "single-file.rs": "fn helper() {}"
+ }),
+ )
+ .await;
+
+ let project = Project::test(
+ fs,
+ [
+ Path::new(path!("/root/single-file.rs")),
+ Path::new(path!("/root/dir-project")),
+ ],
+ cx,
+ )
+ .await;
+
+ let work_dirs = project.read_with(cx, |project, cx| project.default_path_list(cx));
+ let ordered_paths = work_dirs.ordered_paths().cloned().collect::<Vec<_>>();
+
+ assert_eq!(
+ ordered_paths,
+ vec![
+ PathBuf::from(path!("/root/dir-project")),
+ PathBuf::from(path!("/root")),
+ ]
+ );
+}
+
+#[gpui::test]
+async fn test_default_session_work_dirs_falls_back_to_home_for_empty_project(
+ cx: &mut gpui::TestAppContext,
+) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+
+ let work_dirs = project.read_with(cx, |project, cx| project.default_path_list(cx));
+ let ordered_paths = work_dirs.ordered_paths().cloned().collect::<Vec<_>>();
+
+ assert_eq!(ordered_paths, vec![paths::home_dir().to_path_buf()]);
+}
+
// NOTE:
// While POSIX symbolic links are somewhat supported on Windows, they are an opt in by the user, and thus
// we assume that they are not supported out of the box.
@@ -103,11 +103,11 @@ use {
feature_flags::FeatureFlagAppExt as _,
git_ui::project_diff::ProjectDiff,
gpui::{
- App, AppContext as _, Bounds, KeyBinding, Modifiers, VisualTestAppContext, WindowBounds,
- WindowHandle, WindowOptions, point, px, size,
+ App, AppContext as _, Bounds, Entity, KeyBinding, Modifiers, VisualTestAppContext,
+ WindowBounds, WindowHandle, WindowOptions, point, px, size,
},
image::RgbaImage,
- project::AgentId,
+ project::{AgentId, Project},
project_panel::ProjectPanel,
settings::{NotifyWhenAgentWaiting, Settings as _},
settings_ui::SettingsWindow,
@@ -1966,6 +1966,7 @@ impl AgentServer for StubAgentServer {
fn connect(
&self,
_delegate: AgentServerDelegate,
+ _project: Entity<Project>,
_cx: &mut App,
) -> gpui::Task<gpui::Result<Rc<dyn AgentConnection>>> {
gpui::Task::ready(Ok(Rc::new(self.connection.clone())))