@@ -32,9 +32,9 @@ pub fn init(cx: &mut AppContext) {
impl FileFinder {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
- dbg!("yay");
+ dbg!("REGISTERING");
workspace.register_action(|workspace, _: &Toggle, cx| {
- dbg!("yayer");
+ dbg!("CALLING ACTION");
let Some(file_finder) = workspace.current_modal::<Self>(cx) else {
Self::open(workspace, cx);
return;
@@ -738,1459 +738,1236 @@ 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::{TestAppContext, ViewHandle};
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// cx.dispatch_action(window.into(), Toggle);
-
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder.delegate_mut().update_matches("bna".to_string(), cx)
-// })
-// .await;
-// finder.read_with(cx, |finder, _| {
-// assert_eq!(finder.delegate().matches.len(), 2);
-// });
-
-// let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-// cx.dispatch_action(window.into(), SelectNext);
-// cx.dispatch_action(window.into(), 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
-// .as_any()
-// .downcast_ref::<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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// cx.dispatch_action(window.into(), Toggle);
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-
-// 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}");
-// finder
-// .update(cx, |finder, cx| {
-// finder
-// .delegate_mut()
-// .update_matches(query_inside_file.to_string(), cx)
-// })
-// .await;
-// finder.read_with(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(window.into(), SelectNext);
-// cx.dispatch_action(window.into(), 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.foreground().advance_clock(Duration::from_secs(2));
-// cx.foreground().start_waiting();
-// cx.foreground().finish_waiting();
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// cx.dispatch_action(window.into(), Toggle);
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-
-// 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}");
-// finder
-// .update(cx, |finder, cx| {
-// finder
-// .delegate_mut()
-// .update_matches(query_outside_file.to_string(), cx)
-// })
-// .await;
-// finder.read_with(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_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(window.into(), SelectNext);
-// cx.dispatch_action(window.into(), 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.foreground().advance_clock(Duration::from_secs(2));
-// cx.foreground().start_waiting();
-// cx.foreground().finish_waiting();
-// 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 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);
-
-// let query = test_path_like("hi");
-// finder
-// .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
-// .await;
-// finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
-
-// finder.update(cx, |finder, cx| {
-// let delegate = finder.delegate_mut();
-// 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 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_mut().spawn_search(test_path_like("hi"), cx)
-// })
-// .await;
-// finder.read_with(cx, |f, _| assert_eq!(f.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 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);
-
-// // Even though there is only one worktree, that worktree's filename
-// // is included in the matching, because the worktree is a single file.
-// finder
-// .update(cx, |f, cx| {
-// f.delegate_mut().spawn_search(test_path_like("thf"), cx)
-// })
-// .await;
-// cx.read(|cx| {
-// let finder = finder.read(cx);
-// let delegate = finder.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.
-// finder
-// .update(cx, |f, cx| {
-// f.delegate_mut().spawn_search(test_path_like("thf/"), cx)
-// })
-// .await;
-// finder.read_with(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
-// .add_window(|cx| Workspace::test_new(project, cx))
-// .root(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")),
-// }));
-// 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_mut().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_mut().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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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();
-// deterministic.run_until_parked();
-
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(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();
-// deterministic.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.id() != worktree_id.to_usize())
-// .expect("New worktree should have a different id")
-// .id(),
-// )
-// });
-// close_active_item(&workspace, &deterministic, cx).await;
-
-// let initial_history_items = open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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",
-// window.into(),
-// &workspace,
-// &deterministic,
-// 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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-
-// // generate some history to select from
-// open_close_queried_buffer(
-// "fir",
-// 1,
-// "first.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "thi",
-// 1,
-// "third.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// let current_history = open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-
-// for expected_selected_index in 0..current_history.len() {
-// cx.dispatch_action(window.into(), Toggle);
-// let selected_index = cx.read(|cx| {
-// workspace
-// .read(cx)
-// .modal::<FileFinder>()
-// .unwrap()
-// .read(cx)
-// .delegate()
-// .selected_index()
-// });
-// assert_eq!(
-// selected_index, expected_selected_index,
-// "Should select the next item in the history"
-// );
-// }
-
-// cx.dispatch_action(window.into(), Toggle);
-// let selected_index = cx.read(|cx| {
-// workspace
-// .read(cx)
-// .modal::<FileFinder>()
-// .unwrap()
-// .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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(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())
-// });
-
-// // generate some history to select from
-// open_close_queried_buffer(
-// "fir",
-// 1,
-// "first.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "thi",
-// 1,
-// "third.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-
-// cx.dispatch_action(window.into(), Toggle);
-// let first_query = "f";
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder
-// .delegate_mut()
-// .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 = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder
-// .delegate_mut()
-// .update_matches(second_query.to_string(), cx)
-// })
-// .await;
-// finder.read_with(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 = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder
-// .delegate_mut()
-// .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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// // generate some history to select from
-// open_close_queried_buffer(
-// "fir",
-// 1,
-// "first.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "thi",
-// 1,
-// "third.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "sec",
-// 1,
-// "second.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-
-// cx.dispatch_action(window.into(), Toggle);
-// let query = "collab_ui";
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder.delegate_mut().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(
-// deterministic: Arc<gpui::executor::Deterministic>,
-// 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 window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// // generate some history to select from
-// open_close_queried_buffer(
-// "fir",
-// 1,
-// "first.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "non",
-// 1,
-// "nonexistent.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "thi",
-// 1,
-// "third.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-// open_close_queried_buffer(
-// "fir",
-// 1,
-// "first.rs",
-// window.into(),
-// &workspace,
-// &deterministic,
-// cx,
-// )
-// .await;
-
-// cx.dispatch_action(window.into(), Toggle);
-// let query = "rs";
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder.delegate_mut().update_matches(query.to_string(), cx)
-// })
-// .await;
-// finder.read_with(cx, |finder, _| {
-// let delegate = finder.delegate();
-// let history_entries = 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,
-// window: gpui::AnyWindowHandle,
-// workspace: &ViewHandle<Workspace>,
-// deterministic: &gpui::executor::Deterministic,
-// cx: &mut gpui::TestAppContext,
-// ) -> Vec<FoundPath> {
-// cx.dispatch_action(window, Toggle);
-// let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
-// finder
-// .update(cx, |finder, cx| {
-// finder.delegate_mut().update_matches(input.to_string(), cx)
-// })
-// .await;
-// let history_items = finder.read_with(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(window, SelectNext);
-// cx.dispatch_action(window, Confirm);
-// deterministic.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
-// .as_any()
-// .downcast_ref::<Editor>()
-// .unwrap()
-// .read(cx)
-// .title(cx);
-// assert_eq!(
-// expected_editor_title, active_editor_title,
-// "Unexpected editor title for query {input}"
-// );
-// });
-
-// close_active_item(workspace, deterministic, cx).await;
-
-// history_items
-// }
-
-// async fn close_active_item(
-// workspace: &ViewHandle<Workspace>,
-// deterministic: &gpui::executor::Deterministic,
-// cx: &mut TestAppContext,
-// ) {
-// let mut original_items = HashMap::new();
-// cx.read(|cx| {
-// for pane in workspace.read(cx).panes() {
-// let pane_id = pane.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();
-// deterministic.run_until_parked();
-// cx.read(|cx| {
-// for pane in workspace.read(cx).panes() {
-// let pane_id = pane.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.foreground_executor().forbid_parking();
-// 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,
-// }
-// }
-// }
+#[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;
+ 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)
+ }
+}