diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 38c5e8cdb5..6a453b7a5c 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -734,6 +734,101 @@ mod tests { ); } + #[gpui::test] + async fn test_reopen_existing_remote_root_treats_root_as_directory( + cx: &mut TestAppContext, + server_cx: &mut TestAppContext, + ) { + let app_state = init_test(cx); + let executor = cx.executor(); + + cx.update(|cx| { + release_channel::init(semver::Version::new(0, 0, 0), cx); + }); + server_cx.update(|cx| { + release_channel::init(semver::Version::new(0, 0, 0), cx); + }); + + let (opts, server_session, connect_guard) = RemoteClient::fake_server(cx, server_cx); + + let remote_fs = FakeFs::new(server_cx.executor()); + let remote_home = paths::home_dir(); + let canonical_project_path = remote_home.join("remote-reopen-root-project"); + remote_fs + .insert_tree( + &canonical_project_path, + json!({ + "src": { + "main.rs": "fn main() {}", + }, + "README.md": "# Test Project", + }), + ) + .await; + + server_cx.update(HeadlessProject::init); + let http_client = Arc::new(BlockedHttpClient); + let node_runtime = NodeRuntime::unavailable(); + let languages = Arc::new(language::LanguageRegistry::new(server_cx.executor())); + let proxy = Arc::new(ExtensionHostProxy::new()); + + let _headless = server_cx.new(|cx| { + HeadlessProject::new( + HeadlessAppState { + session: server_session, + fs: remote_fs.clone(), + http_client, + node_runtime, + languages, + extension_host_proxy: proxy, + startup_time: std::time::Instant::now(), + }, + false, + cx, + ) + }); + + drop(connect_guard); + + let mut async_cx = cx.to_async(); + let window = open_remote_project( + opts, + vec![canonical_project_path.clone()], + app_state, + workspace::OpenOptions::default(), + &mut async_cx, + ) + .await + .expect("initial open_remote_project should succeed"); + + executor.run_until_parked(); + + let open_results = window + .update(cx, |multi_workspace, window, cx| { + let workspace = multi_workspace.workspace().clone(); + workspace.update(cx, |workspace, cx| { + workspace.open_paths( + vec![canonical_project_path.clone()], + workspace::OpenOptions { + visible: Some(workspace::OpenVisible::All), + ..Default::default() + }, + None, + window, + cx, + ) + }) + }) + .unwrap() + .await; + + assert_eq!(open_results.len(), 1, "should return one open result"); + assert!( + open_results[0].is_none(), + "reopening a remote root directory should not try to open it as a file" + ); + } + #[gpui::test] async fn test_reconnect_when_server_not_running( cx: &mut TestAppContext, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index dbb18703c5..c275b8720e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3672,8 +3672,23 @@ impl Workspace { let fs = fs.clone(); let pane = pane.clone(); let task = cx.spawn(async move |cx| { - let (_worktree, project_path) = project_path?; - if fs.is_dir(&abs_path).await { + let (worktree, project_path) = project_path?; + let (entry_is_directory, worktree_is_local) = + worktree.read_with(cx, |worktree, _| { + let entry = if project_path.path.as_unix_str().is_empty() { + worktree.root_entry() + } else { + worktree.entry_for_path(&project_path.path) + }; + (entry.map(|entry| entry.is_dir()), worktree.is_local()) + }); + let is_directory = match entry_is_directory { + Some(is_directory) => is_directory, + None if worktree_is_local => fs.is_dir(&abs_path).await, + None => false, + }; + + if is_directory { // Opening a directory should not race to update the active entry. // We'll select/reveal a deterministic final entry after all paths finish opening. None