file_finder: Display single files already opened (#39911)

Coenen Benjamin and Kirill Bulatov created

Closes https://github.com/zed-industries/zed/issues/24670

(Follow up of https://github.com/zed-industries/zed/pull/36856) cc
@ConradIrwin Thanks for your help

Release Notes:

Fixed: Keep non project files when filtering in File finder

---------

Signed-off-by: Benjamin <5719034+bnjjj@users.noreply.github.com>
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>

Change summary

crates/file_finder/src/file_finder.rs       |  4 +
crates/file_finder/src/file_finder_tests.rs | 61 ++++++++++++++++++++++
crates/project/src/worktree_store.rs        |  9 +++
3 files changed, 71 insertions(+), 3 deletions(-)

Detailed changes

crates/file_finder/src/file_finder.rs 🔗

@@ -866,7 +866,9 @@ impl FileFinderDelegate {
         let worktrees = self
             .project
             .read(cx)
-            .visible_worktrees(cx)
+            .worktree_store()
+            .read(cx)
+            .visible_worktrees_and_single_files(cx)
             .collect::<Vec<_>>();
         let include_root_name = worktrees.len() > 1;
         let candidate_sets = worktrees

crates/file_finder/src/file_finder_tests.rs 🔗

@@ -8,7 +8,7 @@ use pretty_assertions::{assert_eq, assert_matches};
 use project::{FS_WATCH_LATENCY, RemoveOptions};
 use serde_json::json;
 use util::{path, rel_path::rel_path};
-use workspace::{AppState, CloseActiveItem, OpenOptions, ToggleFileFinder, Workspace};
+use workspace::{AppState, CloseActiveItem, OpenOptions, ToggleFileFinder, Workspace, open_paths};
 
 #[ctor::ctor]
 fn init_logger() {
@@ -2337,7 +2337,6 @@ async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAp
         assert_match_at_position(finder, 1, "main.rs");
         assert_match_at_position(finder, 2, "rs");
     });
-
     // Delete main.rs
     app_state
         .fs
@@ -2370,6 +2369,64 @@ async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAp
     });
 }
 
+#[gpui::test]
+async fn test_search_results_refreshed_on_standalone_file_creation(cx: &mut gpui::TestAppContext) {
+    let app_state = init_test(cx);
+
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            "/src",
+            json!({
+                "lib.rs": "// Lib file",
+                "main.rs": "// Bar file",
+                "read.me": "// Readme file",
+            }),
+        )
+        .await;
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            "/test",
+            json!({
+                "new.rs": "// New file",
+            }),
+        )
+        .await;
+
+    let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+    let (workspace, cx) =
+        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+    cx.update(|_, cx| {
+        open_paths(
+            &[PathBuf::from(path!("/test/new.rs"))],
+            app_state,
+            workspace::OpenOptions::default(),
+            cx,
+        )
+    })
+    .await
+    .unwrap();
+    assert_eq!(cx.update(|_, cx| cx.windows().len()), 1);
+
+    let initial_history = open_close_queried_buffer("new", 1, "new.rs", &workspace, cx).await;
+    assert_eq!(
+        initial_history.first().unwrap().absolute,
+        PathBuf::from(path!("/test/new.rs")),
+        "Should show 1st opened item in the history when opening the 2nd item"
+    );
+
+    let history_after_first = open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
+    assert_eq!(
+        history_after_first.first().unwrap().absolute,
+        PathBuf::from(path!("/test/new.rs")),
+        "Should show 1st opened item in the history when opening the 2nd item"
+    );
+}
+
 #[gpui::test]
 async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
     cx: &mut gpui::TestAppContext,

crates/project/src/worktree_store.rs 🔗

@@ -138,6 +138,15 @@ impl WorktreeStore {
             .filter(|worktree| worktree.read(cx).is_visible())
     }
 
+    /// Iterates through all user-visible worktrees (directories and files that appear in the project panel) and other, invisible single files that could appear e.g. due to drag and drop.
+    pub fn visible_worktrees_and_single_files<'a>(
+        &'a self,
+        cx: &'a App,
+    ) -> impl 'a + DoubleEndedIterator<Item = Entity<Worktree>> {
+        self.worktrees()
+            .filter(|worktree| worktree.read(cx).is_visible() || worktree.read(cx).is_single_file())
+    }
+
     pub fn worktree_for_id(&self, id: WorktreeId, cx: &App) -> Option<Entity<Worktree>> {
         self.worktrees()
             .find(|worktree| worktree.read(cx).id() == id)