Preserve selected entry in file_finder (#13452)

Closes #11737

If the query has not changed and entry is still in the matches list keep
it selected


Release Notes:

- Fixes file finder jumping during background updates ([#11737](https://github.com/zed-industries/zed/issues/11737))
This commit is contained in:
kshokhin 2024-08-27 16:24:06 +03:00 committed by GitHub
parent 60af9dd4b1
commit 5586397f95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 375 additions and 127 deletions

View file

@ -1323,6 +1323,62 @@ async fn test_history_items_shown_in_order_of_open(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_selected_history_item_stays_selected_on_worktree_updated(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/test",
json!({
"test": {
"1.txt": "// One",
"2.txt": "// Two",
"3.txt": "// Three",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
open_close_queried_buffer("1", 1, "1.txt", &workspace, cx).await;
open_close_queried_buffer("2", 1, "2.txt", &workspace, cx).await;
open_close_queried_buffer("3", 1, "3.txt", &workspace, cx).await;
let picker = open_file_picker(&workspace, cx);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_selection(finder, 0, "3.txt");
assert_match_at_position(finder, 1, "2.txt");
assert_match_at_position(finder, 2, "1.txt");
});
cx.dispatch_action(SelectNext);
// Add more files to the worktree to trigger update matches
for i in 0..5 {
let filename = format!("/test/{}.txt", 4 + i);
app_state
.fs
.create_file(Path::new(&filename), Default::default())
.await
.expect("unable to create file");
}
cx.executor().advance_clock(FS_WATCH_LATENCY);
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "3.txt");
assert_match_selection(finder, 1, "2.txt");
assert_match_at_position(finder, 2, "1.txt");
});
}
#[gpui::test]
async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
@ -1541,6 +1597,107 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
});
}
#[gpui::test]
async fn test_selected_match_stays_selected_after_matches_refreshed(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
app_state.fs.as_fake().insert_tree("/src", json!({})).await;
app_state
.fs
.create_dir("/src/even".as_ref())
.await
.expect("unable to create dir");
let initial_files_num = 5;
for i in 0..initial_files_num {
let filename = format!("/src/even/file_{}.txt", 10 + i);
app_state
.fs
.create_file(Path::new(&filename), Default::default())
.await
.expect("unable to create file");
}
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("file");
let selected_index = 3;
// Checking only the filename, not the whole path
let selected_file = format!("file_{}.txt", 10 + selected_index);
// Select even/file_13.txt
for _ in 0..selected_index {
cx.dispatch_action(SelectNext);
}
picker.update(cx, |finder, _| {
assert_match_selection(finder, selected_index, &selected_file)
});
// Add more matches to the search results
let files_to_add = 10;
for i in 0..files_to_add {
let filename = format!("/src/file_{}.txt", 20 + i);
app_state
.fs
.create_file(Path::new(&filename), Default::default())
.await
.expect("unable to create file");
}
cx.executor().advance_clock(FS_WATCH_LATENCY);
// file_13.txt is still selected
picker.update(cx, |finder, _| {
let expected_selected_index = selected_index + files_to_add;
assert_match_selection(finder, expected_selected_index, &selected_file);
});
}
#[gpui::test]
async fn test_first_match_selected_if_previous_one_is_not_in_the_match_list(
cx: &mut gpui::TestAppContext,
) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/src",
json!({
"file_1.txt": "// file_1",
"file_2.txt": "// file_2",
"file_3.txt": "// file_3",
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("file");
// Select even/file_2.txt
cx.dispatch_action(SelectNext);
// Remove the selected entry
app_state
.fs
.remove_file("/src/file_2.txt".as_ref(), Default::default())
.await
.expect("unable to remove file");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// file_1.txt is now selected
picker.update(cx, |finder, _| {
assert_match_selection(finder, 0, "file_1.txt");
});
}
#[gpui::test]
async fn test_keeps_file_finder_open_after_modifier_keys_release(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
@ -1940,8 +2097,11 @@ impl SearchEntries {
fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries {
let mut search_entries = SearchEntries::default();
for m in &picker.delegate.matches.matches {
match m {
Match::History(history_path, path_match) => {
match &m {
Match::History {
path: history_path,
panel_match: path_match,
} => {
search_entries.history.push(
path_match
.as_ref()
@ -1996,8 +2156,8 @@ fn assert_match_at_position(
.matches
.get(match_index)
.unwrap_or_else(|| panic!("Finder has no match for index {match_index}"));
let match_file_name = match match_item {
Match::History(found_path, _) => found_path.absolute.as_deref().unwrap().file_name(),
let match_file_name = match &match_item {
Match::History { path, .. } => path.absolute.as_deref().unwrap().file_name(),
Match::Search(path_match) => path_match.0.path.file_name(),
}
.unwrap()