diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index 726251e9dee876..ea025a8c277293 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/crates/file_finder/src/file_finder_tests.rs @@ -447,6 +447,55 @@ async fn test_complex_path(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_tail_proximity_bonus_prefers_later_match(cx: &mut TestAppContext) { + let app_state = init_test(cx); + + cx.update(|cx| { + let settings = *ProjectPanelSettings::get_global(cx); + ProjectPanelSettings::override_global( + ProjectPanelSettings { + hide_root: true, + ..settings + }, + cx, + ); + }); + + app_state + .fs + .as_fake() + .insert_tree( + path!("/root"), + json!({ + "zzz": { "zed": { "Cargo.toml": "" } }, + "zed": { "zzz": { "Cargo.toml": "" } }, + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await; + let (picker, _, cx) = build_find_picker(project, cx); + + picker + .update_in(cx, |picker, window, cx| { + picker + .delegate + .update_matches("zed cargo".to_string(), window, cx) + }) + .await; + + picker.update(cx, |picker, _| { + assert_eq!( + collect_search_matches(picker).search_paths_only(), + vec![ + rel_path("zzz/zed/Cargo.toml").into(), + rel_path("zed/zzz/Cargo.toml").into(), + ], + ); + }); +} + #[gpui::test] async fn test_row_column_numbers_query_inside_file(cx: &mut TestAppContext) { let app_state = init_test(cx); diff --git a/crates/fuzzy_nucleo/src/paths.rs b/crates/fuzzy_nucleo/src/paths.rs index 6aaabfeb50ecb4..d59ec3ff2996cb 100644 --- a/crates/fuzzy_nucleo/src/paths.rs +++ b/crates/fuzzy_nucleo/src/paths.rs @@ -134,6 +134,19 @@ fn get_filename_match_bonus( score as f64 / filename.len().max(1) as f64 } +const TAIL_REWARD_SCALE: f64 = 1.5; + +#[inline] +fn get_tail_proximity_bonus(matched_chars: &[u32], haystack_len: u32) -> f64 { + debug_assert!( + haystack_len != 0 && matched_chars.len() != 0, + "This should only be called on a successful match." + ); + let trailing: u32 = matched_chars.iter().map(|&i| haystack_len - 1 - i).sum(); + let mean_trailing = trailing as f64 / matched_chars.len() as f64; + (1.0 / (1.0 + mean_trailing)) * TAIL_REWARD_SCALE +} + fn path_match_helper<'a>( matcher: &mut nucleo::Matcher, query: &Query, @@ -193,7 +206,9 @@ fn path_match_helper<'a>( let length_penalty = candidate_buf.len() as f64 * LENGTH_PENALTY; let filename_bonus = get_filename_match_bonus(&candidate_buf, &query.pattern, matcher); - let positive = (score as f64 + filename_bonus) * case_penalty(case_mismatches); + let tail_proximity_bonus = get_tail_proximity_bonus(&matched_chars, haystack.len() as u32); + let positive = + (score as f64 + filename_bonus + tail_proximity_bonus) * case_penalty(case_mismatches); let adjusted_score = positive - length_penalty; let positions = positions_from_sorted(&candidate_buf, &matched_chars);