workspace: Treat remote roots as directories (#54733)
## Summary - Add a regression test covering reopening an already-open remote workspace root inside the reused workspace path - Use the resolved worktree entry instead of the local filesystem when `Workspace::open_paths` decides whether a reused path is a directory - This fixes the follow-on case where reopening an already-open SSH workspace root could show `Error: opening project path ...` because the remote root was being treated like a local file open I kept this fix in `Workspace::open_paths` rather than adding another remote-specific branch higher up, because the bug was specifically in the generic reused-workspace directory check. ## Testing - `cargo test -p recent_projects test_reopen_existing_remote_root_treats_root_as_directory -- --nocapture` - `cargo test -p recent_projects test_reuse_existing_remote_workspace_window_with_tilde_path -- --nocapture` - `cargo test -p recent_projects test_reuse_existing_remote_workspace_window -- --nocapture` Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] Tests cover the new/changed behaviour - [x] Performance impact has been considered and is acceptable Related to #30829 Release Notes: - Fixed reopening an already-open SSH workspace root showing an `opening project path` error instead of treating the root as a directory
This commit is contained in:
1 parent
f87b2446d8
commit
fd7b2866c5
2 files changed
+112
-2
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user