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.
This commit is contained in:
loadingalias authored and GitHub committed 2026-03-10 14:42:32 +05:30
1 parent d1a323b4ac
commit 8fa257bbe0
2 files changed
+64 -2

No files matched your search

+15 -2
View File
@@ -3403,8 +3403,7 @@ impl ProjectPanel {
_: &mut Window,
cx: &mut Context<Self>,
) {
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<PathBuf> {
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,
@@ -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);