From 8fa257bbe0c975d18ffce2edabfe4a0298f75bfe Mon Sep 17 00:00:00 2001 From: loadingalias <138315197+loadingalias@users.noreply.github.com> Date: Tue, 10 Mar 2026 05:12:32 -0400 Subject: [PATCH] project_panel: Reveal in file manager when no entry is selected (#50866) Closes #48284 ## Summary - Fix `project_panel::RevealInFileManager` when no project panel entry is selected. - Preserve existing selected entry behavior. - Add fallback to reveal the last visible worktree root when selection is empty. - Add regression test cov. ## Root Cause `RevealInFileManager` previously depended on `selected_sub_entry()`. When selection is cleared (e.g. click project panel background), Command Palette dispatch had no target and no-op'd. ## Verification - `cargo fmt --all -- --check` - `./script/check-keymaps` - `./script/clippy -p project_panel` - `cargo test -p project_panel -- --nocapture` ## Manual Testing - Reproduced issue steps from #48284. - Confirmed Command Palette `Project panel: Reveal in file manager` now opens project root when selection is empty. - Confirmed selected file reveal behavior remains unchanged. - Confirmed context menu reveal behavior remains unchanged. Release Notes: - Fixed `Project panel: Reveal in file manager` to work even when no project panel entry is selected. --- crates/project_panel/src/project_panel.rs | 17 ++++++- .../project_panel/src/project_panel_tests.rs | 49 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 55f440852ada15505831c78035d9362c91b4a204..068fb8d71fa883e9d2b518c7d19adacea74fadcb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -3403,8 +3403,7 @@ impl ProjectPanel { _: &mut Window, cx: &mut Context, ) { - if let Some((worktree, entry)) = self.selected_sub_entry(cx) { - let path = worktree.read(cx).absolutize(&entry.path); + if let Some(path) = self.reveal_in_file_manager_path(cx) { self.project .update(cx, |project, cx| project.reveal_path(&path, cx)); } @@ -3761,6 +3760,20 @@ impl ProjectPanel { } Some((worktree, entry)) } + + fn reveal_in_file_manager_path(&self, cx: &App) -> Option { + if let Some((worktree, entry)) = self.selected_sub_entry(cx) { + return Some(worktree.read(cx).absolutize(&entry.path)); + } + + let root_entry_id = self.state.last_worktree_root_id?; + let project = self.project.read(cx); + let worktree = project.worktree_for_entry(root_entry_id, cx)?; + let worktree = worktree.read(cx); + let root_entry = worktree.entry_for_id(root_entry_id)?; + Some(worktree.absolutize(&root_entry.path)) + } + fn selected_entry_handle<'a>( &self, cx: &'a App, diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 64e96fee700aea8277fe1b69121abf71599c4d30..720ac04fdd2a656a32668add23e7af021a71ef00 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -8670,6 +8670,55 @@ async fn test_compare_files_context_menu(cx: &mut gpui::TestAppContext) { } } +#[gpui::test] +async fn test_reveal_in_file_manager_path_falls_back_to_worktree_root( + cx: &mut gpui::TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + "/root", + json!({ + "file.txt": "content", + "dir": {}, + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx)); + let workspace = window + .read_with(cx, |mw, _| mw.workspace().clone()) + .unwrap(); + let cx = &mut VisualTestContext::from_window(window.into(), cx); + let panel = workspace.update_in(cx, ProjectPanel::new); + cx.run_until_parked(); + + select_path(&panel, "root/file.txt", cx); + let selected_reveal_path = panel + .update(cx, |panel, cx| panel.reveal_in_file_manager_path(cx)) + .expect("selected entry should produce a reveal path"); + assert!( + selected_reveal_path.ends_with(Path::new("file.txt")), + "Expected selected file path, got {:?}", + selected_reveal_path + ); + + panel.update(cx, |panel, _| { + panel.selection = None; + panel.marked_entries.clear(); + }); + let fallback_reveal_path = panel + .update(cx, |panel, cx| panel.reveal_in_file_manager_path(cx)) + .expect("project root should be used when selection is empty"); + assert!( + fallback_reveal_path.ends_with(Path::new("root")), + "Expected worktree root path, got {:?}", + fallback_reveal_path + ); +} + #[gpui::test] async fn test_hide_hidden_entries(cx: &mut gpui::TestAppContext) { init_test(cx);