project_panel: Fix selection not updating for already-visible gitignored files (#49521)

Efe created

## Summary
- Keep auto-reveal behavior for ignored files unchanged (no implicit
reveal).
- When an ignored file is already visible in the project panel, mark it
as selected on `ActiveEntryChanged`.
- Add regression coverage for switching back to a visible gitignored
file.

## Testing
- `project_panel_tests::test_autoreveal_and_gitignored_files`
- `project_panel_tests::test_gitignored_and_always_included`
- `project_panel_tests::test_explicit_reveal`

Closes #49515

Release Notes:

- Fixed project panel not updating selection when switching to a
gitignored file that was already visible.

Change summary

crates/project_panel/src/project_panel.rs       | 28 ++++++--
crates/project_panel/src/project_panel_tests.rs | 58 +++++++++++++++++++
2 files changed, 79 insertions(+), 7 deletions(-)

Detailed changes

crates/project_panel/src/project_panel.rs 🔗

@@ -6053,12 +6053,27 @@ impl ProjectPanel {
             .worktree_for_entry(entry_id, cx)
             .context("can't reveal a non-existent entry in the project panel")?;
         let worktree = worktree.read(cx);
-        if skip_ignored
-            && worktree
-                .entry_for_id(entry_id)
-                .is_none_or(|entry| entry.is_ignored && !entry.is_always_included)
-        {
-            anyhow::bail!("can't reveal an ignored entry in the project panel");
+        let worktree_id = worktree.id();
+        let is_ignored = worktree
+            .entry_for_id(entry_id)
+            .is_none_or(|entry| entry.is_ignored && !entry.is_always_included);
+        if skip_ignored && is_ignored {
+            if self.index_for_entry(entry_id, worktree_id).is_none() {
+                anyhow::bail!("can't reveal an ignored entry in the project panel");
+            }
+
+            self.selection = Some(SelectedEntry {
+                worktree_id,
+                entry_id,
+            });
+            self.marked_entries.clear();
+            self.marked_entries.push(SelectedEntry {
+                worktree_id,
+                entry_id,
+            });
+            self.autoscroll(cx);
+            cx.notify();
+            return Ok(());
         }
         let is_active_item_file_diff_view = self
             .workspace
@@ -6070,7 +6085,6 @@ impl ProjectPanel {
             return Ok(());
         }
 
-        let worktree_id = worktree.id();
         self.expand_entry(worktree_id, entry_id, cx);
         self.update_visible_entries(Some((worktree_id, entry_id)), false, true, window, cx);
         self.marked_entries.clear();

crates/project_panel/src/project_panel_tests.rs 🔗

@@ -4843,6 +4843,64 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
         ],
         "When a gitignored entry is explicitly revealed, it should be shown in the project tree"
     );
+
+    panel.update(cx, |panel, cx| {
+        panel.project.update(cx, |_, cx| {
+            cx.emit(project::Event::ActiveEntryChanged(Some(dir_2_file)))
+        })
+    });
+    cx.run_until_parked();
+    assert_eq!(
+        visible_entries_as_strings(&panel, 0..20, cx),
+        &[
+            "v project_root",
+            "    > .git",
+            "    v dir_1",
+            "        v gitignored_dir",
+            "              file_a.py",
+            "              file_b.py",
+            "              file_c.py",
+            "          file_1.py",
+            "          file_2.py",
+            "          file_3.py",
+            "    v dir_2",
+            "          file_1.py  <== selected  <== marked",
+            "          file_2.py",
+            "          file_3.py",
+            "      .gitignore",
+        ],
+        "After switching to dir_2_file, it should be selected and marked"
+    );
+
+    panel.update(cx, |panel, cx| {
+        panel.project.update(cx, |_, cx| {
+            cx.emit(project::Event::ActiveEntryChanged(Some(
+                gitignored_dir_file,
+            )))
+        })
+    });
+    cx.run_until_parked();
+    assert_eq!(
+        visible_entries_as_strings(&panel, 0..20, cx),
+        &[
+            "v project_root",
+            "    > .git",
+            "    v dir_1",
+            "        v gitignored_dir",
+            "              file_a.py  <== selected  <== marked",
+            "              file_b.py",
+            "              file_c.py",
+            "          file_1.py",
+            "          file_2.py",
+            "          file_3.py",
+            "    v dir_2",
+            "          file_1.py",
+            "          file_2.py",
+            "          file_3.py",
+            "      .gitignore",
+        ],
+        "When a gitignored entry is already visible, auto reveal should mark it as selected"
+    );
 }
 
 #[gpui::test]