FileFinder tests (#3336)

Conrad Irwin created

Also including:
* Fixes for focus when closing the last item in a pane
* Workspace#active_item_as::<Editor>()
* cx.simulate_input()

Release Notes:

- N/A

Change summary

crates/command_palette2/src/command_palette.rs |    6 
crates/file_finder2/src/file_finder.rs         | 2335 +++++++++----------
crates/gpui2/src/app/test_context.rs           |   18 
crates/workspace2/src/modal_layer.rs           |    2 
crates/workspace2/src/pane.rs                  |   13 
crates/workspace2/src/workspace2.rs            |   21 
6 files changed, 1,144 insertions(+), 1,251 deletions(-)

Detailed changes

crates/command_palette2/src/command_palette.rs 🔗

@@ -403,7 +403,7 @@ mod tests {
 
         let palette = workspace.update(cx, |workspace, cx| {
             workspace
-                .current_modal::<CommandPalette>(cx)
+                .active_modal::<CommandPalette>(cx)
                 .unwrap()
                 .read(cx)
                 .picker
@@ -426,7 +426,7 @@ mod tests {
         cx.simulate_keystrokes("enter");
 
         workspace.update(cx, |workspace, cx| {
-            assert!(workspace.current_modal::<CommandPalette>(cx).is_none());
+            assert!(workspace.active_modal::<CommandPalette>(cx).is_none());
             assert_eq!(editor.read(cx).text(cx), "ab")
         });
 
@@ -443,7 +443,7 @@ mod tests {
 
         let palette = workspace.update(cx, |workspace, cx| {
             workspace
-                .current_modal::<CommandPalette>(cx)
+                .active_modal::<CommandPalette>(cx)
                 .unwrap()
                 .read(cx)
                 .picker

crates/file_finder2/src/file_finder.rs 🔗

@@ -34,7 +34,7 @@ pub fn init(cx: &mut AppContext) {
 impl FileFinder {
     fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
         workspace.register_action(|workspace, _: &Toggle, cx| {
-            let Some(file_finder) = workspace.current_modal::<Self>(cx) else {
+            let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
                 Self::open(workspace, cx);
                 return;
             };
@@ -738,1236 +738,1103 @@ impl PickerDelegate for FileFinderDelegate {
     }
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use std::{assert_eq, collections::HashMap, path::Path, time::Duration};
-
-//     use super::*;
-//     use editor::Editor;
-//     use gpui::{Entity, TestAppContext, VisualTestContext};
-//     use menu::{Confirm, SelectNext};
-//     use serde_json::json;
-//     use workspace::{AppState, Workspace};
-
-//     #[ctor::ctor]
-//     fn init_logger() {
-//         if std::env::var("RUST_LOG").is_ok() {
-//             env_logger::init();
-//         }
-//     }
-
-//     #[gpui::test]
-//     async fn test_matching_paths(cx: &mut TestAppContext) {
-//         let app_state = init_test(cx);
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/root",
-//                 json!({
-//                     "a": {
-//                         "banana": "",
-//                         "bandana": "",
-//                     }
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-
-//         let (picker, workspace, mut cx) = build_find_picker(project, cx);
-//         let cx = &mut cx;
-
-//         picker
-//             .update(cx, |picker, cx| {
-//                 picker.delegate.update_matches("bna".to_string(), cx)
-//             })
-//             .await;
-
-//         picker.update(cx, |picker, _| {
-//             assert_eq!(picker.delegate.matches.len(), 2);
-//         });
-
-//         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-//         cx.dispatch_action(SelectNext);
-//         cx.dispatch_action(Confirm);
-//         active_pane
-//             .condition(cx, |pane, _| pane.active_item().is_some())
-//             .await;
-//         cx.read(|cx| {
-//             let active_item = active_pane.read(cx).active_item().unwrap();
-//             assert_eq!(
-//                 active_item
-//                     .to_any()
-//                     .downcast::<Editor>()
-//                     .unwrap()
-//                     .read(cx)
-//                     .title(cx),
-//                 "bandana"
-//             );
-//         });
-//     }
-
-//     #[gpui::test]
-//     async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
-//         let app_state = init_test(cx);
-
-//         let first_file_name = "first.rs";
-//         let first_file_contents = "// First Rust file";
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/src",
-//                 json!({
-//                     "test": {
-//                         first_file_name: first_file_contents,
-//                         "second.rs": "// Second Rust file",
-//                     }
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
-//         let (picker, workspace, mut cx) = build_find_picker(project, cx);
-//         let cx = &mut cx;
-
-//         let file_query = &first_file_name[..3];
-//         let file_row = 1;
-//         let file_column = 3;
-//         assert!(file_column <= first_file_contents.len());
-//         let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
-//         picker
-//             .update(cx, |finder, cx| {
-//                 finder
-//                     .delegate
-//                     .update_matches(query_inside_file.to_string(), cx)
-//             })
-//             .await;
-//         picker.update(cx, |finder, _| {
-//             let finder = &finder.delegate;
-//             assert_eq!(finder.matches.len(), 1);
-//             let latest_search_query = finder
-//                 .latest_search_query
-//                 .as_ref()
-//                 .expect("Finder should have a query after the update_matches call");
-//             assert_eq!(latest_search_query.path_like.raw_query, query_inside_file);
-//             assert_eq!(
-//                 latest_search_query.path_like.file_query_end,
-//                 Some(file_query.len())
-//             );
-//             assert_eq!(latest_search_query.row, Some(file_row));
-//             assert_eq!(latest_search_query.column, Some(file_column as u32));
-//         });
-
-//         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-//         cx.dispatch_action(SelectNext);
-//         cx.dispatch_action(Confirm);
-//         active_pane
-//             .condition(cx, |pane, _| pane.active_item().is_some())
-//             .await;
-//         let editor = cx.update(|cx| {
-//             let active_item = active_pane.read(cx).active_item().unwrap();
-//             active_item.downcast::<Editor>().unwrap()
-//         });
-//         cx.executor().advance_clock(Duration::from_secs(2));
-
-//         editor.update(cx, |editor, cx| {
-//             let all_selections = editor.selections.all_adjusted(cx);
-//             assert_eq!(
-//                 all_selections.len(),
-//                 1,
-//                 "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-//             );
-//             let caret_selection = all_selections.into_iter().next().unwrap();
-//             assert_eq!(caret_selection.start, caret_selection.end,
-//                 "Caret selection should have its start and end at the same position");
-//             assert_eq!(file_row, caret_selection.start.row + 1,
-//                 "Query inside file should get caret with the same focus row");
-//             assert_eq!(file_column, caret_selection.start.column as usize + 1,
-//                 "Query inside file should get caret with the same focus column");
-//         });
-//     }
-
-//     #[gpui::test]
-//     async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
-//         let app_state = init_test(cx);
-
-//         let first_file_name = "first.rs";
-//         let first_file_contents = "// First Rust file";
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/src",
-//                 json!({
-//                     "test": {
-//                         first_file_name: first_file_contents,
-//                         "second.rs": "// Second Rust file",
-//                     }
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-
-//         let (picker, workspace, mut cx) = build_find_picker(project, cx);
-//         let cx = &mut cx;
-
-//         let file_query = &first_file_name[..3];
-//         let file_row = 200;
-//         let file_column = 300;
-//         assert!(file_column > first_file_contents.len());
-//         let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
-//         picker
-//             .update(cx, |picker, cx| {
-//                 picker
-//                     .delegate
-//                     .update_matches(query_outside_file.to_string(), cx)
-//             })
-//             .await;
-//         picker.update(cx, |finder, _| {
-//             let delegate = &finder.delegate;
-//             assert_eq!(delegate.matches.len(), 1);
-//             let latest_search_query = delegate
-//                 .latest_search_query
-//                 .as_ref()
-//                 .expect("Finder should have a query after the update_matches call");
-//             assert_eq!(latest_search_query.path_like.raw_query, query_outside_file);
-//             assert_eq!(
-//                 latest_search_query.path_like.file_query_end,
-//                 Some(file_query.len())
-//             );
-//             assert_eq!(latest_search_query.row, Some(file_row));
-//             assert_eq!(latest_search_query.column, Some(file_column as u32));
-//         });
-
-//         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-//         cx.dispatch_action(SelectNext);
-//         cx.dispatch_action(Confirm);
-//         active_pane
-//             .condition(cx, |pane, _| pane.active_item().is_some())
-//             .await;
-//         let editor = cx.update(|cx| {
-//             let active_item = active_pane.read(cx).active_item().unwrap();
-//             active_item.downcast::<Editor>().unwrap()
-//         });
-//         cx.executor().advance_clock(Duration::from_secs(2));
-
-//         editor.update(cx, |editor, cx| {
-//             let all_selections = editor.selections.all_adjusted(cx);
-//             assert_eq!(
-//                 all_selections.len(),
-//                 1,
-//                 "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
-//             );
-//             let caret_selection = all_selections.into_iter().next().unwrap();
-//             assert_eq!(caret_selection.start, caret_selection.end,
-//                 "Caret selection should have its start and end at the same position");
-//             assert_eq!(0, caret_selection.start.row,
-//                 "Excessive rows (as in query outside file borders) should get trimmed to last file row");
-//             assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
-//                 "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
-//         });
-//     }
-
-//     #[gpui::test]
-//     async fn test_matching_cancellation(cx: &mut TestAppContext) {
-//         let app_state = init_test(cx);
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/dir",
-//                 json!({
-//                     "hello": "",
-//                     "goodbye": "",
-//                     "halogen-light": "",
-//                     "happiness": "",
-//                     "height": "",
-//                     "hi": "",
-//                     "hiccup": "",
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
-
-//         let (picker, _, mut cx) = build_find_picker(project, cx);
-//         let cx = &mut cx;
-
-//         let query = test_path_like("hi");
-//         picker
-//             .update(cx, |picker, cx| {
-//                 picker.delegate.spawn_search(query.clone(), cx)
-//             })
-//             .await;
-
-//         picker.update(cx, |picker, _cx| {
-//             assert_eq!(picker.delegate.matches.len(), 5)
-//         });
-
-//         picker.update(cx, |picker, cx| {
-//             let delegate = &mut picker.delegate;
-//             assert!(
-//                 delegate.matches.history.is_empty(),
-//                 "Search matches expected"
-//             );
-//             let matches = delegate.matches.search.clone();
-
-//             // Simulate a search being cancelled after the time limit,
-//             // returning only a subset of the matches that would have been found.
-//             drop(delegate.spawn_search(query.clone(), cx));
-//             delegate.set_search_matches(
-//                 delegate.latest_search_id,
-//                 true, // did-cancel
-//                 query.clone(),
-//                 vec![matches[1].clone(), matches[3].clone()],
-//                 cx,
-//             );
-
-//             // Simulate another cancellation.
-//             drop(delegate.spawn_search(query.clone(), cx));
-//             delegate.set_search_matches(
-//                 delegate.latest_search_id,
-//                 true, // did-cancel
-//                 query.clone(),
-//                 vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
-//                 cx,
-//             );
-
-//             assert!(
-//                 delegate.matches.history.is_empty(),
-//                 "Search matches expected"
-//             );
-//             assert_eq!(delegate.matches.search.as_slice(), &matches[0..4]);
-//         });
-//     }
-
-//     #[gpui::test]
-//     async fn test_ignored_files(cx: &mut TestAppContext) {
-//         let app_state = init_test(cx);
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/ancestor",
-//                 json!({
-//                     ".gitignore": "ignored-root",
-//                     "ignored-root": {
-//                         "happiness": "",
-//                         "height": "",
-//                         "hi": "",
-//                         "hiccup": "",
-//                     },
-//                     "tracked-root": {
-//                         ".gitignore": "height",
-//                         "happiness": "",
-//                         "height": "",
-//                         "hi": "",
-//                         "hiccup": "",
-//                     },
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(
-//             app_state.fs.clone(),
-//             [
-//                 "/ancestor/tracked-root".as_ref(),
-//                 "/ancestor/ignored-root".as_ref(),
-//             ],
-//             cx,
-//         )
-//         .await;
-
-//         let (picker, _, mut cx) = build_find_picker(project, cx);
-//         let cx = &mut cx;
-
-//         picker
-//             .update(cx, |picker, cx| {
-//                 picker.delegate.spawn_search(test_path_like("hi"), cx)
-//             })
-//             .await;
-//         picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
-//     }
-
-// #[gpui::test]
-// async fn test_single_file_worktrees(cx: &mut TestAppContext) {
-//     let app_state = init_test(cx);
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
-//         .await;
-
-//     let project = Project::test(
-//         app_state.fs.clone(),
-//         ["/root/the-parent-dir/the-file".as_ref()],
-//         cx,
-//     )
-//     .await;
-
-//     let (picker, _, mut cx) = build_find_picker(project, cx);
-//     let cx = &mut cx;
-
-//     // Even though there is only one worktree, that worktree's filename
-//     // is included in the matching, because the worktree is a single file.
-//     picker
-//         .update(cx, |picker, cx| {
-//             picker.delegate.spawn_search(test_path_like("thf"), cx)
-//         })
-//         .await;
-//     cx.read(|cx| {
-//         let picker = picker.read(cx);
-//         let delegate = &picker.delegate;
-//         assert!(
-//             delegate.matches.history.is_empty(),
-//             "Search matches expected"
-//         );
-//         let matches = delegate.matches.search.clone();
-//         assert_eq!(matches.len(), 1);
-
-//         let (file_name, file_name_positions, full_path, full_path_positions) =
-//             delegate.labels_for_path_match(&matches[0]);
-//         assert_eq!(file_name, "the-file");
-//         assert_eq!(file_name_positions, &[0, 1, 4]);
-//         assert_eq!(full_path, "the-file");
-//         assert_eq!(full_path_positions, &[0, 1, 4]);
-//     });
-
-//     // Since the worktree root is a file, searching for its name followed by a slash does
-//     // not match anything.
-//     picker
-//         .update(cx, |f, cx| {
-//             f.delegate.spawn_search(test_path_like("thf/"), cx)
-//         })
-//         .await;
-//     picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
-// }
-
-// #[gpui::test]
-// async fn test_path_distance_ordering(cx: &mut TestAppContext) {
-//     let app_state = init_test(cx);
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/root",
-//             json!({
-//                 "dir1": { "a.txt": "" },
-//                 "dir2": {
-//                     "a.txt": "",
-//                     "b.txt": ""
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-//     let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-
-//     let worktree_id = cx.read(|cx| {
-//         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-//         assert_eq!(worktrees.len(), 1);
-//         WorktreeId::from_usize(worktrees[0].id())
-//     });
-
-//     // When workspace has an active item, sort items which are closer to that item
-//     // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
-//     // so that one should be sorted earlier
-//     let b_path = Some(dummy_found_path(ProjectPath {
-//         worktree_id,
-//         path: Arc::from(Path::new("/root/dir2/b.txt")),
-//     }));
-//     cx.dispatch_action(Toggle);
-
-//     let finder = cx
-//         .add_window(|cx| {
-//             Picker::new(
-//                 FileFinderDelegate::new(
-//                     workspace.downgrade(),
-//                     workspace.read(cx).project().clone(),
-//                     b_path,
-//                     Vec::new(),
-//                     cx,
-//                 ),
-//                 cx,
-//             )
-//         })
-//         .root(cx);
-
-//     finder
-//         .update(cx, |f, cx| {
-//             f.delegate.spawn_search(test_path_like("a.txt"), cx)
-//         })
-//         .await;
-
-//     finder.read_with(cx, |f, _| {
-//         let delegate = &f.delegate;
-//         assert!(
-//             delegate.matches.history.is_empty(),
-//             "Search matches expected"
-//         );
-//         let matches = delegate.matches.search.clone();
-//         assert_eq!(matches[0].path.as_ref(), Path::new("dir2/a.txt"));
-//         assert_eq!(matches[1].path.as_ref(), Path::new("dir1/a.txt"));
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
-//     let app_state = init_test(cx);
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/root",
-//             json!({
-//                 "dir1": {},
-//                 "dir2": {
-//                     "dir3": {}
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-//     let workspace = cx
-//         .add_window(|cx| Workspace::test_new(project, cx))
-//         .root(cx);
-//     let finder = cx
-//         .add_window(|cx| {
-//             Picker::new(
-//                 FileFinderDelegate::new(
-//                     workspace.downgrade(),
-//                     workspace.read(cx).project().clone(),
-//                     None,
-//                     Vec::new(),
-//                     cx,
-//                 ),
-//                 cx,
-//             )
-//         })
-//         .root(cx);
-//     finder
-//         .update(cx, |f, cx| {
-//             f.delegate.spawn_search(test_path_like("dir"), cx)
-//         })
-//         .await;
-//     cx.read(|cx| {
-//         let finder = finder.read(cx);
-//         assert_eq!(finder.delegate.matches.len(), 0);
-//     });
-// }
-
-//     #[gpui::test]
-//     async fn test_query_history(cx: &mut gpui::TestAppContext) {
-//         let app_state = init_test(cx);
-
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree(
-//                 "/src",
-//                 json!({
-//                     "test": {
-//                         "first.rs": "// First Rust file",
-//                         "second.rs": "// Second Rust file",
-//                         "third.rs": "// Third Rust file",
-//                     }
-//                 }),
-//             )
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//         let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//         let cx = &mut cx;
-//         let worktree_id = cx.read(|cx| {
-//             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-//             assert_eq!(worktrees.len(), 1);
-//             WorktreeId::from_usize(worktrees[0].id())
-//         });
-
-//         // Open and close panels, getting their history items afterwards.
-//         // Ensure history items get populated with opened items, and items are kept in a certain order.
-//         // The history lags one opened buffer behind, since it's updated in the search panel only on its reopen.
-//         //
-//         // TODO: without closing, the opened items do not propagate their history changes for some reason
-//         // it does work in real app though, only tests do not propagate.
-
-//         let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//         assert!(
-//             initial_history.is_empty(),
-//             "Should have no history before opening any files"
-//         );
-
-//         let history_after_first =
-//             open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//         assert_eq!(
-//             history_after_first,
-//             vec![FoundPath::new(
-//                 ProjectPath {
-//                     worktree_id,
-//                     path: Arc::from(Path::new("test/first.rs")),
-//                 },
-//                 Some(PathBuf::from("/src/test/first.rs"))
-//             )],
-//             "Should show 1st opened item in the history when opening the 2nd item"
-//         );
-
-//         let history_after_second =
-//             open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//         assert_eq!(
-//             history_after_second,
-//             vec![
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/second.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/second.rs"))
-//                 ),
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/first.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/first.rs"))
-//                 ),
-//             ],
-//             "Should show 1st and 2nd opened items in the history when opening the 3rd item. \
-// 2nd item should be the first in the history, as the last opened."
-//         );
-
-//         let history_after_third =
-//             open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//         assert_eq!(
-//             history_after_third,
-//             vec![
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/third.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/third.rs"))
-//                 ),
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/second.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/second.rs"))
-//                 ),
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/first.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/first.rs"))
-//                 ),
-//             ],
-//             "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
-// 3rd item should be the first in the history, as the last opened."
-//         );
-
-//         let history_after_second_again =
-//             open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//         assert_eq!(
-//             history_after_second_again,
-//             vec![
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/second.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/second.rs"))
-//                 ),
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/third.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/third.rs"))
-//                 ),
-//                 FoundPath::new(
-//                     ProjectPath {
-//                         worktree_id,
-//                         path: Arc::from(Path::new("test/first.rs")),
-//                     },
-//                     Some(PathBuf::from("/src/test/first.rs"))
-//                 ),
-//             ],
-//             "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
-// 2nd item, as the last opened, 3rd item should go next as it was opened right before."
-//         );
-//     }
-
-// #[gpui::test]
-// async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
-//     let app_state = init_test(cx);
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/src",
-//             json!({
-//                 "test": {
-//                     "first.rs": "// First Rust file",
-//                     "second.rs": "// Second Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/external-src",
-//             json!({
-//                 "test": {
-//                     "third.rs": "// Third Rust file",
-//                     "fourth.rs": "// Fourth Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//     cx.update(|cx| {
-//         project.update(cx, |project, cx| {
-//             project.find_or_create_local_worktree("/external-src", false, cx)
-//         })
-//     })
-//     .detach();
-//     cx.background_executor.run_until_parked();
-
-//     let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-//     let worktree_id = cx.read(|cx| {
-//         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-//         assert_eq!(worktrees.len(), 1,);
-
-//         WorktreeId::from_usize(worktrees[0].id())
-//     });
-//     workspace
-//         .update(cx, |workspace, cx| {
-//             workspace.open_abs_path(PathBuf::from("/external-src/test/third.rs"), false, cx)
-//         })
-//         .detach();
-//     cx.background_executor.run_until_parked();
-//     let external_worktree_id = cx.read(|cx| {
-//         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-//         assert_eq!(
-//             worktrees.len(),
-//             2,
-//             "External file should get opened in a new worktree"
-//         );
-
-//         WorktreeId::from_usize(
-//             worktrees
-//                 .into_iter()
-//                 .find(|worktree| worktree.entity_id() != worktree_id.to_usize())
-//                 .expect("New worktree should have a different id")
-//                 .id(),
-//         )
-//     });
-//     close_active_item(&workspace, cx).await;
-
-//     let initial_history_items =
-//         open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//     assert_eq!(
-//         initial_history_items,
-//         vec![FoundPath::new(
-//             ProjectPath {
-//                 worktree_id: external_worktree_id,
-//                 path: Arc::from(Path::new("")),
-//             },
-//             Some(PathBuf::from("/external-src/test/third.rs"))
-//         )],
-//         "Should show external file with its full path in the history after it was open"
-//     );
-
-//     let updated_history_items =
-//         open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//     assert_eq!(
-//         updated_history_items,
-//         vec![
-//             FoundPath::new(
-//                 ProjectPath {
-//                     worktree_id,
-//                     path: Arc::from(Path::new("test/second.rs")),
-//                 },
-//                 Some(PathBuf::from("/src/test/second.rs"))
-//             ),
-//             FoundPath::new(
-//                 ProjectPath {
-//                     worktree_id: external_worktree_id,
-//                     path: Arc::from(Path::new("")),
-//                 },
-//                 Some(PathBuf::from("/external-src/test/third.rs"))
-//             ),
-//         ],
-//         "Should keep external file with history updates",
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
-//     let app_state = init_test(cx);
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/src",
-//             json!({
-//                 "test": {
-//                     "first.rs": "// First Rust file",
-//                     "second.rs": "// Second Rust file",
-//                     "third.rs": "// Third Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//     let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-
-//     // generate some history to select from
-//     open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//     cx.executor().run_until_parked();
-//     open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//     open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//     let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-//     for expected_selected_index in 0..current_history.len() {
-//         cx.dispatch_action(Toggle);
-//         let selected_index = workspace.update(cx, |workspace, cx| {
-//             workspace
-//                 .current_modal::<FileFinder>(cx)
-//                 .unwrap()
-//                 .read(cx)
-//                 .picker
-//                 .read(cx)
-//                 .delegate
-//                 .selected_index()
-//         });
-//         assert_eq!(
-//             selected_index, expected_selected_index,
-//             "Should select the next item in the history"
-//         );
-//     }
-
-//     cx.dispatch_action(Toggle);
-//     let selected_index = workspace.update(cx, |workspace, cx| {
-//         workspace
-//             .current_modal::<FileFinder>(cx)
-//             .unwrap()
-//             .read(cx)
-//             .picker
-//             .read(cx)
-//             .delegate
-//             .selected_index()
-//     });
-//     assert_eq!(
-//         selected_index, 0,
-//         "Should wrap around the history and start all over"
-//     );
-// }
-
-// #[gpui::test]
-// async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
-//     let app_state = init_test(cx);
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/src",
-//             json!({
-//                 "test": {
-//                     "first.rs": "// First Rust file",
-//                     "second.rs": "// Second Rust file",
-//                     "third.rs": "// Third Rust file",
-//                     "fourth.rs": "// Fourth Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//     let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-//     let worktree_id = cx.read(|cx| {
-//         let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
-//         assert_eq!(worktrees.len(), 1,);
-
-//         WorktreeId::from_usize(worktrees[0].entity_id())
-//     });
-
-//     // generate some history to select from
-//     open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//     open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//     open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//     open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-//     cx.dispatch_action(Toggle);
-//     let first_query = "f";
-//     let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-//     finder
-//         .update(cx, |finder, cx| {
-//             finder.delegate.update_matches(first_query.to_string(), cx)
-//         })
-//         .await;
-//     finder.read_with(cx, |finder, _| {
-//         let delegate = &finder.delegate;
-//         assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
-//         let history_match = delegate.matches.history.first().unwrap();
-//         assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
-//         assert_eq!(history_match.0, FoundPath::new(
-//             ProjectPath {
-//                 worktree_id,
-//                 path: Arc::from(Path::new("test/first.rs")),
-//             },
-//             Some(PathBuf::from("/src/test/first.rs"))
-//         ));
-//         assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
-//         assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
-//     });
-
-//     let second_query = "fsdasdsa";
-//     let finder = workspace.update(cx, |workspace, cx| {
-//         workspace
-//             .current_modal::<FileFinder>(cx)
-//             .unwrap()
-//             .read(cx)
-//             .picker
-//     });
-//     finder
-//         .update(cx, |finder, cx| {
-//             finder.delegate.update_matches(second_query.to_string(), cx)
-//         })
-//         .await;
-//     finder.update(cx, |finder, _| {
-//         let delegate = &finder.delegate;
-//         assert!(
-//             delegate.matches.history.is_empty(),
-//             "No history entries should match {second_query}"
-//         );
-//         assert!(
-//             delegate.matches.search.is_empty(),
-//             "No search entries should match {second_query}"
-//         );
-//     });
-
-//     let first_query_again = first_query;
-
-//     let finder = workspace.update(cx, |workspace, cx| {
-//         workspace
-//             .current_modal::<FileFinder>(cx)
-//             .unwrap()
-//             .read(cx)
-//             .picker
-//     });
-//     finder
-//         .update(cx, |finder, cx| {
-//             finder
-//                 .delegate
-//                 .update_matches(first_query_again.to_string(), cx)
-//         })
-//         .await;
-//     finder.read_with(cx, |finder, _| {
-//         let delegate = &finder.delegate;
-//         assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
-//         let history_match = delegate.matches.history.first().unwrap();
-//         assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
-//         assert_eq!(history_match.0, FoundPath::new(
-//             ProjectPath {
-//                 worktree_id,
-//                 path: Arc::from(Path::new("test/first.rs")),
-//             },
-//             Some(PathBuf::from("/src/test/first.rs"))
-//         ));
-//         assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
-//         assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
-//     let app_state = init_test(cx);
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/src",
-//             json!({
-//                 "collab_ui": {
-//                     "first.rs": "// First Rust file",
-//                     "second.rs": "// Second Rust file",
-//                     "third.rs": "// Third Rust file",
-//                     "collab_ui.rs": "// Fourth Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//     let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-//     // generate some history to select from
-//     open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//     open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-//     open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//     open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
-
-//     cx.dispatch_action(Toggle);
-//     let query = "collab_ui";
-//     let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-//     finder
-//         .update(cx, |finder, cx| {
-//             finder.delegate.update_matches(query.to_string(), cx)
-//         })
-//         .await;
-//     finder.read_with(cx, |finder, _| {
-//         let delegate = &finder.delegate;
-//         assert!(
-//             delegate.matches.history.is_empty(),
-//             "History items should not math query {query}, they should be matched by name only"
-//         );
-
-//         let search_entries = delegate
-//             .matches
-//             .search
-//             .iter()
-//             .map(|path_match| path_match.path.to_path_buf())
-//             .collect::<Vec<_>>();
-//         assert_eq!(
-//             search_entries,
-//             vec![
-//                 PathBuf::from("collab_ui/collab_ui.rs"),
-//                 PathBuf::from("collab_ui/third.rs"),
-//                 PathBuf::from("collab_ui/first.rs"),
-//                 PathBuf::from("collab_ui/second.rs"),
-//             ],
-//             "Despite all search results having the same directory name, the most matching one should be on top"
-//         );
-//     });
-// }
-
-// #[gpui::test]
-// async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) {
-//     let app_state = init_test(cx);
-
-//     app_state
-//         .fs
-//         .as_fake()
-//         .insert_tree(
-//             "/src",
-//             json!({
-//                 "test": {
-//                     "first.rs": "// First Rust file",
-//                     "nonexistent.rs": "// Second Rust file",
-//                     "third.rs": "// Third Rust file",
-//                 }
-//             }),
-//         )
-//         .await;
-
-//     let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-//     let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//     let cx = &mut cx;
-//     // generate some history to select from
-//     open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-//     open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
-//     open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
-//     open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
-
-//     cx.dispatch_action(Toggle);
-//     let query = "rs";
-//     let finder = cx.read(|cx| workspace.read(cx).current_modal::<FileFinder>().unwrap());
-//     finder
-//         .update(cx, |finder, cx| {
-//             finder.picker.update(cx, |picker, cx| {
-//                 picker.delegate.update_matches(query.to_string(), cx)
-//             })
-//         })
-//         .await;
-//     finder.update(cx, |finder, _| {
-//         let history_entries = finder.delegate
-//             .matches
-//             .history
-//             .iter()
-//             .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf())
-//             .collect::<Vec<_>>();
-//         assert_eq!(
-//             history_entries,
-//             vec![
-//                 PathBuf::from("test/first.rs"),
-//                 PathBuf::from("test/third.rs"),
-//             ],
-//             "Should have all opened files in the history, except the ones that do not exist on disk"
-//         );
-//     });
-// }
-
-//     async fn open_close_queried_buffer(
-//         input: &str,
-//         expected_matches: usize,
-//         expected_editor_title: &str,
-//         workspace: &View<Workspace>,
-//         cx: &mut gpui::VisualTestContext<'_>,
-//     ) -> Vec<FoundPath> {
-//         cx.dispatch_action(Toggle);
-//         let picker = workspace.update(cx, |workspace, cx| {
-//             workspace
-//                 .current_modal::<FileFinder>(cx)
-//                 .unwrap()
-//                 .read(cx)
-//                 .picker
-//                 .clone()
-//         });
-//         picker
-//             .update(cx, |finder, cx| {
-//                 finder.delegate.update_matches(input.to_string(), cx)
-//             })
-//             .await;
-//         let history_items = picker.update(cx, |finder, _| {
-//             assert_eq!(
-//                 finder.delegate.matches.len(),
-//                 expected_matches,
-//                 "Unexpected number of matches found for query {input}"
-//             );
-//             finder.delegate.history_items.clone()
-//         });
-
-//         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-//         cx.dispatch_action(SelectNext);
-//         cx.dispatch_action(Confirm);
-//         cx.background_executor.run_until_parked();
-//         active_pane
-//             .condition(cx, |pane, _| pane.active_item().is_some())
-//             .await;
-//         cx.read(|cx| {
-//             let active_item = active_pane.read(cx).active_item().unwrap();
-//             let active_editor_title = active_item
-//                 .to_any()
-//                 .downcast::<Editor>()
-//                 .unwrap()
-//                 .read(cx)
-//                 .title(cx);
-//             assert_eq!(
-//                 expected_editor_title, active_editor_title,
-//                 "Unexpected editor title for query {input}"
-//             );
-//         });
-
-//         close_active_item(workspace, cx).await;
-
-//         history_items
-//     }
-
-//     async fn close_active_item(workspace: &View<Workspace>, cx: &mut VisualTestContext<'_>) {
-//         let mut original_items = HashMap::new();
-//         cx.read(|cx| {
-//             for pane in workspace.read(cx).panes() {
-//                 let pane_id = pane.entity_id();
-//                 let pane = pane.read(cx);
-//                 let insertion_result = original_items.insert(pane_id, pane.items().count());
-//                 assert!(insertion_result.is_none(), "Pane id {pane_id} collision");
-//             }
-//         });
-
-//         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-//         active_pane
-//             .update(cx, |pane, cx| {
-//                 pane.close_active_item(&workspace::CloseActiveItem { save_intent: None }, cx)
-//                     .unwrap()
-//             })
-//             .await
-//             .unwrap();
-//         cx.background_executor.run_until_parked();
-//         cx.read(|cx| {
-//             for pane in workspace.read(cx).panes() {
-//                 let pane_id = pane.entity_id();
-//                 let pane = pane.read(cx);
-//                 match original_items.remove(&pane_id) {
-//                     Some(original_items) => {
-//                         assert_eq!(
-//                             pane.items().count(),
-//                             original_items.saturating_sub(1),
-//                             "Pane id {pane_id} should have item closed"
-//                         );
-//                     }
-//                     None => panic!("Pane id {pane_id} not found in original items"),
-//                 }
-//             }
-//         });
-//         assert!(
-//             original_items.len() <= 1,
-//             "At most one panel should got closed"
-//         );
-//     }
-
-//     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
-//         cx.update(|cx| {
-//             let state = AppState::test(cx);
-//             theme::init(cx);
-//             language::init(cx);
-//             super::init(cx);
-//             editor::init(cx);
-//             workspace::init_settings(cx);
-//             Project::init_settings(cx);
-//             state
-//         })
-//     }
-
-//     fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
-//         PathLikeWithPosition::parse_str(test_str, |path_like_str| {
-//             Ok::<_, std::convert::Infallible>(FileSearchQuery {
-//                 raw_query: test_str.to_owned(),
-//                 file_query_end: if path_like_str == test_str {
-//                     None
-//                 } else {
-//                     Some(path_like_str.len())
-//                 },
-//             })
-//         })
-//         .unwrap()
-//     }
-
-//     fn dummy_found_path(project_path: ProjectPath) -> FoundPath {
-//         FoundPath {
-//             project: project_path,
-//             absolute: None,
-//         }
-//     }
-
-//     fn build_find_picker(
-//         project: Model<Project>,
-//         cx: &mut TestAppContext,
-//     ) -> (
-//         View<Picker<FileFinderDelegate>>,
-//         View<Workspace>,
-//         VisualTestContext,
-//     ) {
-//         let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-//         cx.dispatch_action(Toggle);
-//         let picker = workspace.update(&mut cx, |workspace, cx| {
-//             workspace
-//                 .current_modal::<FileFinder>(cx)
-//                 .unwrap()
-//                 .read(cx)
-//                 .picker
-//                 .clone()
-//         });
-//         (picker, workspace, cx)
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use std::{assert_eq, path::Path, time::Duration};
+
+    use super::*;
+    use editor::Editor;
+    use gpui::{Entity, TestAppContext, VisualTestContext};
+    use menu::{Confirm, SelectNext};
+    use serde_json::json;
+    use workspace::{AppState, Workspace};
+
+    #[ctor::ctor]
+    fn init_logger() {
+        if std::env::var("RUST_LOG").is_ok() {
+            env_logger::init();
+        }
+    }
+
+    #[gpui::test]
+    async fn test_matching_paths(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/root",
+                json!({
+                    "a": {
+                        "banana": "",
+                        "bandana": "",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+
+        let (picker, workspace, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        cx.simulate_input("bna");
+
+        picker.update(cx, |picker, _| {
+            assert_eq!(picker.delegate.matches.len(), 2);
+        });
+
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        cx.read(|cx| {
+            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+            assert_eq!(active_editor.read(cx).title(cx), "bandana");
+        });
+    }
+
+    #[gpui::test]
+    async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+
+        let first_file_name = "first.rs";
+        let first_file_contents = "// First Rust file";
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        first_file_name: first_file_contents,
+                        "second.rs": "// Second Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+
+        let (picker, workspace, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        let file_query = &first_file_name[..3];
+        let file_row = 1;
+        let file_column = 3;
+        assert!(file_column <= first_file_contents.len());
+        let query_inside_file = format!("{file_query}:{file_row}:{file_column}");
+        picker
+            .update(cx, |finder, cx| {
+                finder
+                    .delegate
+                    .update_matches(query_inside_file.to_string(), cx)
+            })
+            .await;
+        picker.update(cx, |finder, _| {
+            let finder = &finder.delegate;
+            assert_eq!(finder.matches.len(), 1);
+            let latest_search_query = finder
+                .latest_search_query
+                .as_ref()
+                .expect("Finder should have a query after the update_matches call");
+            assert_eq!(latest_search_query.path_like.raw_query, query_inside_file);
+            assert_eq!(
+                latest_search_query.path_like.file_query_end,
+                Some(file_query.len())
+            );
+            assert_eq!(latest_search_query.row, Some(file_row));
+            assert_eq!(latest_search_query.column, Some(file_column as u32));
+        });
+
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+        cx.executor().advance_clock(Duration::from_secs(2));
+
+        editor.update(cx, |editor, cx| {
+                let all_selections = editor.selections.all_adjusted(cx);
+                assert_eq!(
+                    all_selections.len(),
+                    1,
+                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+                );
+                let caret_selection = all_selections.into_iter().next().unwrap();
+                assert_eq!(caret_selection.start, caret_selection.end,
+                    "Caret selection should have its start and end at the same position");
+                assert_eq!(file_row, caret_selection.start.row + 1,
+                    "Query inside file should get caret with the same focus row");
+                assert_eq!(file_column, caret_selection.start.column as usize + 1,
+                    "Query inside file should get caret with the same focus column");
+            });
+    }
+
+    #[gpui::test]
+    async fn test_row_column_numbers_query_outside_file(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+
+        let first_file_name = "first.rs";
+        let first_file_contents = "// First Rust file";
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        first_file_name: first_file_contents,
+                        "second.rs": "// Second Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+
+        let (picker, workspace, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        let file_query = &first_file_name[..3];
+        let file_row = 200;
+        let file_column = 300;
+        assert!(file_column > first_file_contents.len());
+        let query_outside_file = format!("{file_query}:{file_row}:{file_column}");
+        picker
+            .update(cx, |picker, cx| {
+                picker
+                    .delegate
+                    .update_matches(query_outside_file.to_string(), cx)
+            })
+            .await;
+        picker.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert_eq!(delegate.matches.len(), 1);
+            let latest_search_query = delegate
+                .latest_search_query
+                .as_ref()
+                .expect("Finder should have a query after the update_matches call");
+            assert_eq!(latest_search_query.path_like.raw_query, query_outside_file);
+            assert_eq!(
+                latest_search_query.path_like.file_query_end,
+                Some(file_query.len())
+            );
+            assert_eq!(latest_search_query.row, Some(file_row));
+            assert_eq!(latest_search_query.column, Some(file_column as u32));
+        });
+
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        let editor = cx.update(|cx| workspace.read(cx).active_item_as::<Editor>(cx).unwrap());
+        cx.executor().advance_clock(Duration::from_secs(2));
+
+        editor.update(cx, |editor, cx| {
+                let all_selections = editor.selections.all_adjusted(cx);
+                assert_eq!(
+                    all_selections.len(),
+                    1,
+                    "Expected to have 1 selection (caret) after file finder confirm, but got: {all_selections:?}"
+                );
+                let caret_selection = all_selections.into_iter().next().unwrap();
+                assert_eq!(caret_selection.start, caret_selection.end,
+                    "Caret selection should have its start and end at the same position");
+                assert_eq!(0, caret_selection.start.row,
+                    "Excessive rows (as in query outside file borders) should get trimmed to last file row");
+                assert_eq!(first_file_contents.len(), caret_selection.start.column as usize,
+                    "Excessive columns (as in query outside file borders) should get trimmed to selected row's last column");
+            });
+    }
+
+    #[gpui::test]
+    async fn test_matching_cancellation(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/dir",
+                json!({
+                    "hello": "",
+                    "goodbye": "",
+                    "halogen-light": "",
+                    "happiness": "",
+                    "height": "",
+                    "hi": "",
+                    "hiccup": "",
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
+
+        let (picker, _, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        let query = test_path_like("hi");
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(query.clone(), cx)
+            })
+            .await;
+
+        picker.update(cx, |picker, _cx| {
+            assert_eq!(picker.delegate.matches.len(), 5)
+        });
+
+        picker.update(cx, |picker, cx| {
+            let delegate = &mut picker.delegate;
+            assert!(
+                delegate.matches.history.is_empty(),
+                "Search matches expected"
+            );
+            let matches = delegate.matches.search.clone();
+
+            // Simulate a search being cancelled after the time limit,
+            // returning only a subset of the matches that would have been found.
+            drop(delegate.spawn_search(query.clone(), cx));
+            delegate.set_search_matches(
+                delegate.latest_search_id,
+                true, // did-cancel
+                query.clone(),
+                vec![matches[1].clone(), matches[3].clone()],
+                cx,
+            );
+
+            // Simulate another cancellation.
+            drop(delegate.spawn_search(query.clone(), cx));
+            delegate.set_search_matches(
+                delegate.latest_search_id,
+                true, // did-cancel
+                query.clone(),
+                vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
+                cx,
+            );
+
+            assert!(
+                delegate.matches.history.is_empty(),
+                "Search matches expected"
+            );
+            assert_eq!(delegate.matches.search.as_slice(), &matches[0..4]);
+        });
+    }
+
+    #[gpui::test]
+    async fn test_ignored_files(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/ancestor",
+                json!({
+                    ".gitignore": "ignored-root",
+                    "ignored-root": {
+                        "happiness": "",
+                        "height": "",
+                        "hi": "",
+                        "hiccup": "",
+                    },
+                    "tracked-root": {
+                        ".gitignore": "height",
+                        "happiness": "",
+                        "height": "",
+                        "hi": "",
+                        "hiccup": "",
+                    },
+                }),
+            )
+            .await;
+
+        let project = Project::test(
+            app_state.fs.clone(),
+            [
+                "/ancestor/tracked-root".as_ref(),
+                "/ancestor/ignored-root".as_ref(),
+            ],
+            cx,
+        )
+        .await;
+
+        let (picker, _, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(test_path_like("hi"), cx)
+            })
+            .await;
+        picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
+    }
+
+    #[gpui::test]
+    async fn test_single_file_worktrees(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
+            .await;
+
+        let project = Project::test(
+            app_state.fs.clone(),
+            ["/root/the-parent-dir/the-file".as_ref()],
+            cx,
+        )
+        .await;
+
+        let (picker, _, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+
+        // Even though there is only one worktree, that worktree's filename
+        // is included in the matching, because the worktree is a single file.
+        picker
+            .update(cx, |picker, cx| {
+                picker.delegate.spawn_search(test_path_like("thf"), cx)
+            })
+            .await;
+        cx.read(|cx| {
+            let picker = picker.read(cx);
+            let delegate = &picker.delegate;
+            assert!(
+                delegate.matches.history.is_empty(),
+                "Search matches expected"
+            );
+            let matches = delegate.matches.search.clone();
+            assert_eq!(matches.len(), 1);
+
+            let (file_name, file_name_positions, full_path, full_path_positions) =
+                delegate.labels_for_path_match(&matches[0]);
+            assert_eq!(file_name, "the-file");
+            assert_eq!(file_name_positions, &[0, 1, 4]);
+            assert_eq!(full_path, "the-file");
+            assert_eq!(full_path_positions, &[0, 1, 4]);
+        });
+
+        // Since the worktree root is a file, searching for its name followed by a slash does
+        // not match anything.
+        picker
+            .update(cx, |f, cx| {
+                f.delegate.spawn_search(test_path_like("thf/"), cx)
+            })
+            .await;
+        picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
+    }
+
+    #[gpui::test]
+    async fn test_path_distance_ordering(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/root",
+                json!({
+                    "dir1": { "a.txt": "" },
+                    "dir2": {
+                        "a.txt": "",
+                        "b.txt": ""
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+
+        let worktree_id = cx.read(|cx| {
+            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+            assert_eq!(worktrees.len(), 1);
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        });
+
+        // When workspace has an active item, sort items which are closer to that item
+        // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
+        // so that one should be sorted earlier
+        let b_path = ProjectPath {
+            worktree_id,
+            path: Arc::from(Path::new("/root/dir2/b.txt")),
+        };
+        workspace
+            .update(cx, |workspace, cx| {
+                workspace.open_path(b_path, None, true, cx)
+            })
+            .await
+            .unwrap();
+        let finder = open_file_picker(&workspace, cx);
+        finder
+            .update(cx, |f, cx| {
+                f.delegate.spawn_search(test_path_like("a.txt"), cx)
+            })
+            .await;
+
+        finder.update(cx, |f, _| {
+            let delegate = &f.delegate;
+            assert!(
+                delegate.matches.history.is_empty(),
+                "Search matches expected"
+            );
+            let matches = delegate.matches.search.clone();
+            assert_eq!(matches[0].path.as_ref(), Path::new("dir2/a.txt"));
+            assert_eq!(matches[1].path.as_ref(), Path::new("dir1/a.txt"));
+        });
+    }
+
+    #[gpui::test]
+    async fn test_search_worktree_without_files(cx: &mut TestAppContext) {
+        let app_state = init_test(cx);
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/root",
+                json!({
+                    "dir1": {},
+                    "dir2": {
+                        "dir3": {}
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+        let (picker, _workspace, mut cx) = build_find_picker(project, cx);
+        let cx = &mut cx;
+        picker
+            .update(cx, |f, cx| {
+                f.delegate.spawn_search(test_path_like("dir"), cx)
+            })
+            .await;
+        cx.read(|cx| {
+            let finder = picker.read(cx);
+            assert_eq!(finder.delegate.matches.len(), 0);
+        });
+    }
+
+    #[gpui::test]
+    async fn test_query_history(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        "first.rs": "// First Rust file",
+                        "second.rs": "// Second Rust file",
+                        "third.rs": "// Third Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+        let worktree_id = cx.read(|cx| {
+            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+            assert_eq!(worktrees.len(), 1);
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        });
+
+        // Open and close panels, getting their history items afterwards.
+        // Ensure history items get populated with opened items, and items are kept in a certain order.
+        // The history lags one opened buffer behind, since it's updated in the search panel only on its reopen.
+        //
+        // TODO: without closing, the opened items do not propagate their history changes for some reason
+        // it does work in real app though, only tests do not propagate.
+        workspace.update(cx, |_, cx| dbg!(cx.focused()));
+
+        let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        assert!(
+            initial_history.is_empty(),
+            "Should have no history before opening any files"
+        );
+
+        let history_after_first =
+            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        assert_eq!(
+            history_after_first,
+            vec![FoundPath::new(
+                ProjectPath {
+                    worktree_id,
+                    path: Arc::from(Path::new("test/first.rs")),
+                },
+                Some(PathBuf::from("/src/test/first.rs"))
+            )],
+            "Should show 1st opened item in the history when opening the 2nd item"
+        );
+
+        let history_after_second =
+            open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        assert_eq!(
+            history_after_second,
+            vec![
+                FoundPath::new(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("test/second.rs")),
+                    },
+                    Some(PathBuf::from("/src/test/second.rs"))
+                ),
+                FoundPath::new(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("test/first.rs")),
+                    },
+                    Some(PathBuf::from("/src/test/first.rs"))
+                ),
+            ],
+            "Should show 1st and 2nd opened items in the history when opening the 3rd item. \
+    2nd item should be the first in the history, as the last opened."
+        );
+
+        let history_after_third =
+            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        assert_eq!(
+                history_after_third,
+                vec![
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/third.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/third.rs"))
+                    ),
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/second.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/second.rs"))
+                    ),
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/first.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/first.rs"))
+                    ),
+                ],
+                "Should show 1st, 2nd and 3rd opened items in the history when opening the 2nd item again. \
+    3rd item should be the first in the history, as the last opened."
+            );
+
+        let history_after_second_again =
+            open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        assert_eq!(
+                history_after_second_again,
+                vec![
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/second.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/second.rs"))
+                    ),
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/third.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/third.rs"))
+                    ),
+                    FoundPath::new(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from(Path::new("test/first.rs")),
+                        },
+                        Some(PathBuf::from("/src/test/first.rs"))
+                    ),
+                ],
+                "Should show 1st, 2nd and 3rd opened items in the history when opening the 3rd item again. \
+    2nd item, as the last opened, 3rd item should go next as it was opened right before."
+            );
+    }
+
+    #[gpui::test]
+    async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        "first.rs": "// First Rust file",
+                        "second.rs": "// Second Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/external-src",
+                json!({
+                    "test": {
+                        "third.rs": "// Third Rust file",
+                        "fourth.rs": "// Fourth Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        cx.update(|cx| {
+            project.update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/external-src", false, cx)
+            })
+        })
+        .detach();
+        cx.background_executor.run_until_parked();
+
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+        let worktree_id = cx.read(|cx| {
+            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+            assert_eq!(worktrees.len(), 1,);
+
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        });
+        workspace
+            .update(cx, |workspace, cx| {
+                workspace.open_abs_path(PathBuf::from("/external-src/test/third.rs"), false, cx)
+            })
+            .detach();
+        cx.background_executor.run_until_parked();
+        let external_worktree_id = cx.read(|cx| {
+            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+            assert_eq!(
+                worktrees.len(),
+                2,
+                "External file should get opened in a new worktree"
+            );
+
+            WorktreeId::from_usize(
+                worktrees
+                    .into_iter()
+                    .find(|worktree| {
+                        worktree.entity_id().as_u64() as usize != worktree_id.to_usize()
+                    })
+                    .expect("New worktree should have a different id")
+                    .entity_id()
+                    .as_u64() as usize,
+            )
+        });
+        cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
+
+        let initial_history_items =
+            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        assert_eq!(
+            initial_history_items,
+            vec![FoundPath::new(
+                ProjectPath {
+                    worktree_id: external_worktree_id,
+                    path: Arc::from(Path::new("")),
+                },
+                Some(PathBuf::from("/external-src/test/third.rs"))
+            )],
+            "Should show external file with its full path in the history after it was open"
+        );
+
+        let updated_history_items =
+            open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        assert_eq!(
+            updated_history_items,
+            vec![
+                FoundPath::new(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(Path::new("test/second.rs")),
+                    },
+                    Some(PathBuf::from("/src/test/second.rs"))
+                ),
+                FoundPath::new(
+                    ProjectPath {
+                        worktree_id: external_worktree_id,
+                        path: Arc::from(Path::new("")),
+                    },
+                    Some(PathBuf::from("/external-src/test/third.rs"))
+                ),
+            ],
+            "Should keep external file with history updates",
+        );
+    }
+
+    #[gpui::test]
+    async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        "first.rs": "// First Rust file",
+                        "second.rs": "// Second Rust file",
+                        "third.rs": "// Third Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+
+        // generate some history to select from
+        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        cx.executor().run_until_parked();
+        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        let current_history =
+            open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+
+        for expected_selected_index in 0..current_history.len() {
+            cx.dispatch_action(Toggle);
+            let picker = active_file_picker(&workspace, cx);
+            let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
+            assert_eq!(
+                selected_index, expected_selected_index,
+                "Should select the next item in the history"
+            );
+        }
+
+        cx.dispatch_action(Toggle);
+        let selected_index = workspace.update(cx, |workspace, cx| {
+            workspace
+                .active_modal::<FileFinder>(cx)
+                .unwrap()
+                .read(cx)
+                .picker
+                .read(cx)
+                .delegate
+                .selected_index()
+        });
+        assert_eq!(
+            selected_index, 0,
+            "Should wrap around the history and start all over"
+        );
+    }
+
+    #[gpui::test]
+    async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        "first.rs": "// First Rust file",
+                        "second.rs": "// Second Rust file",
+                        "third.rs": "// Third Rust file",
+                        "fourth.rs": "// Fourth Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+        let worktree_id = cx.read(|cx| {
+            let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+            assert_eq!(worktrees.len(), 1,);
+
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize)
+        });
+
+        // generate some history to select from
+        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+
+        let finder = open_file_picker(&workspace, cx);
+        let first_query = "f";
+        finder
+            .update(cx, |finder, cx| {
+                finder.delegate.update_matches(first_query.to_string(), cx)
+            })
+            .await;
+        finder.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
+            let history_match = delegate.matches.history.first().unwrap();
+            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
+            assert_eq!(history_match.0, FoundPath::new(
+                ProjectPath {
+                    worktree_id,
+                    path: Arc::from(Path::new("test/first.rs")),
+                },
+                Some(PathBuf::from("/src/test/first.rs"))
+            ));
+            assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
+            assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
+        });
+
+        let second_query = "fsdasdsa";
+        let finder = active_file_picker(&workspace, cx);
+        finder
+            .update(cx, |finder, cx| {
+                finder.delegate.update_matches(second_query.to_string(), cx)
+            })
+            .await;
+        finder.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert!(
+                delegate.matches.history.is_empty(),
+                "No history entries should match {second_query}"
+            );
+            assert!(
+                delegate.matches.search.is_empty(),
+                "No search entries should match {second_query}"
+            );
+        });
+
+        let first_query_again = first_query;
+
+        let finder = active_file_picker(&workspace, cx);
+        finder
+            .update(cx, |finder, cx| {
+                finder
+                    .delegate
+                    .update_matches(first_query_again.to_string(), cx)
+            })
+            .await;
+        finder.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
+            let history_match = delegate.matches.history.first().unwrap();
+            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
+            assert_eq!(history_match.0, FoundPath::new(
+                ProjectPath {
+                    worktree_id,
+                    path: Arc::from(Path::new("test/first.rs")),
+                },
+                Some(PathBuf::from("/src/test/first.rs"))
+            ));
+            assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
+            assert_eq!(delegate.matches.search.first().unwrap().path.as_ref(), Path::new("test/fourth.rs"));
+        });
+    }
+
+    #[gpui::test]
+    async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "collab_ui": {
+                        "first.rs": "// First Rust file",
+                        "second.rs": "// Second Rust file",
+                        "third.rs": "// Third Rust file",
+                        "collab_ui.rs": "// Fourth Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+        // generate some history to select from
+        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
+
+        let finder = open_file_picker(&workspace, cx);
+        let query = "collab_ui";
+        cx.simulate_input(query);
+        finder.update(cx, |finder, _| {
+            let delegate = &finder.delegate;
+            assert!(
+                delegate.matches.history.is_empty(),
+                "History items should not math query {query}, they should be matched by name only"
+            );
+
+            let search_entries = delegate
+                .matches
+                .search
+                .iter()
+                .map(|path_match| path_match.path.to_path_buf())
+                .collect::<Vec<_>>();
+            assert_eq!(
+                search_entries,
+                vec![
+                    PathBuf::from("collab_ui/collab_ui.rs"),
+                    PathBuf::from("collab_ui/third.rs"),
+                    PathBuf::from("collab_ui/first.rs"),
+                    PathBuf::from("collab_ui/second.rs"),
+                ],
+                "Despite all search results having the same directory name, the most matching one should be on top"
+            );
+        });
+    }
+
+    #[gpui::test]
+    async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext) {
+        let app_state = init_test(cx);
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/src",
+                json!({
+                    "test": {
+                        "first.rs": "// First Rust file",
+                        "nonexistent.rs": "// Second Rust file",
+                        "third.rs": "// Third Rust file",
+                    }
+                }),
+            )
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let cx = &mut cx;
+        // generate some history to select from
+        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+        open_close_queried_buffer("non", 1, "nonexistent.rs", &workspace, cx).await;
+        open_close_queried_buffer("thi", 1, "third.rs", &workspace, cx).await;
+        open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
+
+        let picker = open_file_picker(&workspace, cx);
+        cx.simulate_input("rs");
+
+        picker.update(cx, |finder, _| {
+            let history_entries = finder.delegate
+                .matches
+                .history
+                .iter()
+                .map(|(_, path_match)| path_match.as_ref().expect("should have a path match").path.to_path_buf())
+                .collect::<Vec<_>>();
+            assert_eq!(
+                history_entries,
+                vec![
+                    PathBuf::from("test/first.rs"),
+                    PathBuf::from("test/third.rs"),
+                ],
+                "Should have all opened files in the history, except the ones that do not exist on disk"
+            );
+        });
+    }
+
+    async fn open_close_queried_buffer(
+        input: &str,
+        expected_matches: usize,
+        expected_editor_title: &str,
+        workspace: &View<Workspace>,
+        cx: &mut gpui::VisualTestContext<'_>,
+    ) -> Vec<FoundPath> {
+        let picker = open_file_picker(&workspace, cx);
+        cx.simulate_input(input);
+
+        let history_items = picker.update(cx, |finder, _| {
+            assert_eq!(
+                finder.delegate.matches.len(),
+                expected_matches,
+                "Unexpected number of matches found for query {input}"
+            );
+            finder.delegate.history_items.clone()
+        });
+
+        cx.dispatch_action(SelectNext);
+        cx.dispatch_action(Confirm);
+
+        cx.read(|cx| {
+            let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+            let active_editor_title = active_editor.read(cx).title(cx);
+            assert_eq!(
+                expected_editor_title, active_editor_title,
+                "Unexpected editor title for query {input}"
+            );
+        });
+
+        cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
+
+        history_items
+    }
+
+    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+        cx.update(|cx| {
+            let state = AppState::test(cx);
+            theme::init(cx);
+            language::init(cx);
+            super::init(cx);
+            editor::init(cx);
+            workspace::init_settings(cx);
+            Project::init_settings(cx);
+            state
+        })
+    }
+
+    fn test_path_like(test_str: &str) -> PathLikeWithPosition<FileSearchQuery> {
+        PathLikeWithPosition::parse_str(test_str, |path_like_str| {
+            Ok::<_, std::convert::Infallible>(FileSearchQuery {
+                raw_query: test_str.to_owned(),
+                file_query_end: if path_like_str == test_str {
+                    None
+                } else {
+                    Some(path_like_str.len())
+                },
+            })
+        })
+        .unwrap()
+    }
+
+    fn build_find_picker(
+        project: Model<Project>,
+        cx: &mut TestAppContext,
+    ) -> (
+        View<Picker<FileFinderDelegate>>,
+        View<Workspace>,
+        VisualTestContext,
+    ) {
+        let (workspace, mut cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let picker = open_file_picker(&workspace, &mut cx);
+        (picker, workspace, cx)
+    }
+
+    #[track_caller]
+    fn open_file_picker(
+        workspace: &View<Workspace>,
+        cx: &mut VisualTestContext,
+    ) -> View<Picker<FileFinderDelegate>> {
+        cx.dispatch_action(Toggle);
+        active_file_picker(workspace, cx)
+    }
+
+    #[track_caller]
+    fn active_file_picker(
+        workspace: &View<Workspace>,
+        cx: &mut VisualTestContext,
+    ) -> View<Picker<FileFinderDelegate>> {
+        workspace.update(cx, |workspace, cx| {
+            workspace
+                .active_modal::<FileFinder>(cx)
+                .unwrap()
+                .read(cx)
+                .picker
+                .clone()
+        })
+    }
+}

crates/gpui2/src/app/test_context.rs 🔗

@@ -225,6 +225,9 @@ impl TestAppContext {
         self.background_executor.run_until_parked()
     }
 
+    /// simulate_keystrokes takes a space-separated list of keys to type.
+    /// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
+    /// will run backspace on the current editor through the command palette.
     pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
         for keystroke in keystrokes
             .split(" ")
@@ -237,6 +240,17 @@ impl TestAppContext {
         self.background_executor.run_until_parked()
     }
 
+    /// simulate_input takes a string of text to type.
+    /// cx.simulate_input("abc")
+    /// will type abc into your current editor.
+    pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
+        for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
+            self.dispatch_keystroke(window, keystroke.into(), false);
+        }
+
+        self.background_executor.run_until_parked()
+    }
+
     pub fn dispatch_keystroke(
         &mut self,
         window: AnyWindowHandle,
@@ -455,6 +469,10 @@ impl<'a> VisualTestContext<'a> {
     pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
         self.cx.simulate_keystrokes(self.window, keystrokes)
     }
+
+    pub fn simulate_input(&mut self, input: &str) {
+        self.cx.simulate_input(self.window, input)
+    }
 }
 
 impl<'a> Context for VisualTestContext<'a> {

crates/workspace2/src/modal_layer.rs 🔗

@@ -72,7 +72,7 @@ impl ModalLayer {
         cx.notify();
     }
 
-    pub fn current_modal<V>(&self) -> Option<View<V>>
+    pub fn active_modal<V>(&self) -> Option<View<V>>
     where
         V: 'static,
     {

crates/workspace2/src/pane.rs 🔗

@@ -8,8 +8,8 @@ use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
     actions, prelude::*, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
-    EventEmitter, FocusHandle, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext,
-    WeakView, WindowContext,
+    EventEmitter, FocusHandle, Focusable, Model, PromptLevel, Render, Task, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -1017,7 +1017,11 @@ impl Pane {
                 .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
 
             let should_activate = activate_pane || self.has_focus(cx);
-            self.activate_item(index_to_activate, should_activate, should_activate, cx);
+            if self.items.len() == 1 && should_activate {
+                self.focus_handle.focus(cx);
+            } else {
+                self.activate_item(index_to_activate, should_activate, should_activate, cx);
+            }
         }
 
         let item = self.items.remove(item_index);
@@ -1913,11 +1917,12 @@ impl Pane {
 // }
 
 impl Render for Pane {
-    type Element = Div<Self>;
+    type Element = Focusable<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack()
             .key_context("Pane")
+            .track_focus(&self.focus_handle)
             .size_full()
             .on_action(|pane: &mut Self, action, cx| {
                 pane.close_active_item(action, cx)

crates/workspace2/src/workspace2.rs 🔗

@@ -38,9 +38,9 @@ use futures::{
 use gpui::{
     actions, div, point, prelude::*, rems, size, Action, AnyModel, AnyView, AnyWeakView,
     AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId,
-    EventEmitter, FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentComponent,
-    Point, Render, Size, Styled, Subscription, Task, View, ViewContext, WeakView, WindowBounds,
-    WindowContext, WindowHandle, WindowOptions,
+    EventEmitter, GlobalPixels, KeyContext, Model, ModelContext, ParentComponent, Point, Render,
+    Size, Styled, Subscription, Task, View, ViewContext, WeakView, WindowBounds, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -433,7 +433,6 @@ pub enum Event {
 
 pub struct Workspace {
     weak_self: WeakView<Self>,
-    focus_handle: FocusHandle,
     workspace_actions: Vec<Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>>,
     zoomed: Option<AnyWeakView>,
     zoomed_position: Option<DockPosition>,
@@ -651,7 +650,6 @@ impl Workspace {
         cx.defer(|this, cx| this.update_window_title(cx));
         Workspace {
             weak_self: weak_handle.clone(),
-            focus_handle: cx.focus_handle(),
             zoomed: None,
             zoomed_position: None,
             center: PaneGroup::new(center_pane.clone()),
@@ -1450,6 +1448,11 @@ impl Workspace {
         self.active_pane().read(cx).active_item()
     }
 
+    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
+        let item = self.active_item(cx)?;
+        item.to_any().downcast::<I>().ok()
+    }
+
     fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
         self.active_item(cx).and_then(|item| item.project_path(cx))
     }
@@ -1570,7 +1573,7 @@ impl Workspace {
         }
 
         if focus_center {
-            cx.focus(&self.focus_handle);
+            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
         }
 
         cx.notify();
@@ -1704,7 +1707,7 @@ impl Workspace {
         }
 
         if focus_center {
-            cx.focus(&self.focus_handle);
+            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
         }
 
         if self.zoomed_position != dock_to_reveal {
@@ -3475,8 +3478,8 @@ impl Workspace {
         div
     }
 
-    pub fn current_modal<V: Modal + 'static>(&mut self, cx: &ViewContext<Self>) -> Option<View<V>> {
-        self.modal_layer.read(cx).current_modal()
+    pub fn active_modal<V: Modal + 'static>(&mut self, cx: &ViewContext<Self>) -> Option<View<V>> {
+        self.modal_layer.read(cx).active_modal()
     }
 
     pub fn toggle_modal<V: Modal, B>(&mut self, cx: &mut ViewContext<Self>, build: B)