Skip to content

Commit 2838ea3

Browse files
Show both local and remote checkouts in Recent Projects (#53953)
This fixes #53917. Recent Projects was treating local and remote workspaces as the same entry whenever they resolved to the same checkout path. That meant one could hide the other, and if one of them was already open, the picker could end up hiding both. This change keeps the deduplication and picker filtering keyed on both the workspace location and the path list, so local and remote copies of the same repo can both appear when they should. ## Test plan - `rustfmt --edition 2024 crates/recent_projects/src/recent_projects.rs crates/workspace/src/persistence.rs` - `git diff --check -- crates/recent_projects/src/recent_projects.rs crates/workspace/src/persistence.rs` Release Notes: - Fixed Recent Projects hiding either the local or remote workspace when both used the same checkout path. Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
1 parent 75ac36d commit 2838ea3

1 file changed

Lines changed: 65 additions & 8 deletions

File tree

crates/recent_projects/src/recent_projects.rs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use fs::Fs;
1717
#[cfg(target_os = "windows")]
1818
mod wsl_picker;
1919

20-
use remote::RemoteConnectionOptions;
20+
use remote::{RemoteConnectionOptions, same_remote_connection_identity};
2121
pub use remote_connection::{RemoteConnectionModal, connect, connect_with_modal};
2222
pub use remote_connections::{navigate_to_positions, open_remote_project};
2323

@@ -45,8 +45,8 @@ use ui::{
4545
};
4646
use util::{ResultExt, paths::PathExt};
4747
use workspace::{
48-
HistoryManager, ModalView, MultiWorkspace, OpenMode, OpenOptions, OpenVisible, PathList,
49-
RecentWorkspace, SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId,
48+
HistoryManager, ModalView, MultiWorkspace, OpenMode, OpenOptions, OpenVisible, RecentWorkspace,
49+
SerializedWorkspaceLocation, Workspace, WorkspaceDb, WorkspaceId,
5050
notifications::DetachAndPromptErr, with_active_or_new_workspace,
5151
};
5252
use zed_actions::{OpenDevContainer, OpenRecent, OpenRemote};
@@ -2433,14 +2433,24 @@ impl RecentProjectsDelegate {
24332433
.any(|key| key.matches(&workspace.project_group_key()))
24342434
}
24352435

2436-
fn is_open_folder(&self, paths: &PathList) -> bool {
2436+
fn is_open_folder(&self, workspace: &RecentWorkspace) -> bool {
24372437
if self.open_folders.is_empty() {
24382438
return false;
24392439
}
24402440

2441-
for workspace_path in paths.paths() {
2441+
let workspace_host = match &workspace.location {
2442+
SerializedWorkspaceLocation::Local => None,
2443+
SerializedWorkspaceLocation::Remote(options) => Some(options),
2444+
};
2445+
2446+
for workspace_path in workspace.paths.paths() {
24422447
for open_folder in &self.open_folders {
2443-
if workspace_path == &open_folder.path {
2448+
if workspace_path == &open_folder.path
2449+
&& same_remote_connection_identity(
2450+
workspace_host,
2451+
open_folder.connection_options.as_ref(),
2452+
)
2453+
{
24442454
return true;
24452455
}
24462456
}
@@ -2456,7 +2466,7 @@ impl RecentProjectsDelegate {
24562466
) -> bool {
24572467
!self.is_current_workspace(workspace.workspace_id, cx)
24582468
&& !self.is_in_current_window_groups(workspace)
2459-
&& !self.is_open_folder(&workspace.paths)
2469+
&& !self.is_open_folder(workspace)
24602470
}
24612471
}
24622472

@@ -2467,7 +2477,7 @@ mod tests {
24672477
use serde_json::json;
24682478
use settings::SettingsStore;
24692479
use util::path;
2470-
use workspace::{AppState, open_paths};
2480+
use workspace::{AppState, PathList, open_paths};
24712481

24722482
use super::*;
24732483

@@ -2679,6 +2689,53 @@ mod tests {
26792689
);
26802690
}
26812691

2692+
#[gpui::test]
2693+
fn is_open_folder_distinguishes_local_and_remote(cx: &mut TestAppContext) {
2694+
init_test(cx);
2695+
2696+
let shared_path = PathBuf::from("/repo");
2697+
let local_open_folder = OpenFolderEntry {
2698+
worktree_id: WorktreeId::from_usize(0),
2699+
name: "repo".into(),
2700+
path: shared_path.clone(),
2701+
branch: None,
2702+
is_active: false,
2703+
connection_options: None,
2704+
};
2705+
2706+
let delegate = RecentProjectsDelegate::new(
2707+
WeakEntity::new_invalid(),
2708+
false,
2709+
cx.update(|cx| cx.focus_handle()),
2710+
vec![local_open_folder],
2711+
Vec::new(),
2712+
ProjectPickerStyle::Modal,
2713+
);
2714+
2715+
let paths = PathList::new(&[shared_path]);
2716+
let local_workspace = RecentWorkspace {
2717+
workspace_id: WorkspaceId::from_i64(1),
2718+
location: SerializedWorkspaceLocation::Local,
2719+
paths: paths.clone(),
2720+
identity_paths: paths.clone(),
2721+
timestamp: Utc::now(),
2722+
};
2723+
let remote_workspace = RecentWorkspace {
2724+
workspace_id: WorkspaceId::from_i64(2),
2725+
location: SerializedWorkspaceLocation::Remote(RemoteConnectionOptions::Mock(
2726+
remote::MockConnectionOptions { id: 0 },
2727+
)),
2728+
paths: paths.clone(),
2729+
identity_paths: paths,
2730+
timestamp: Utc::now(),
2731+
};
2732+
2733+
// A local open folder should hide only the matching local recent
2734+
// project, not a remote checkout that shares the same path.
2735+
assert!(delegate.is_open_folder(&local_workspace));
2736+
assert!(!delegate.is_open_folder(&remote_workspace));
2737+
}
2738+
26822739
#[gpui::test]
26832740
fn deleting_top_recent_project_preserves_scroll_position(cx: &mut TestAppContext) {
26842741
let target = FIRST_RECENT_PROJECT;

0 commit comments

Comments
 (0)