@@ -90,12 +90,12 @@ fn test_sort_matches_local_variable_over_global_variable(_cx: &mut TestAppContex
);
assert_eq!(
matches[2].string_match.string.as_str(),
- "floorf128",
+ "floorf16",
"Match order not expected"
);
assert_eq!(
matches[3].string_match.string.as_str(),
- "floorf16",
+ "floorf32",
"Match order not expected"
);
@@ -433,7 +433,51 @@ fn test_sort_matches_for_unreachable(_cx: &mut TestAppContext) {
"Match order not expected"
);
- // Case 4: "unreachable"
+ // Case 4: "unreachabl"
+ let query: Option<&str> = Some("unreachable");
+ let mut matches: Vec<SortableMatch<'_>> = vec![
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.9090909090909092,
+ positions: vec![],
+ string: "unreachable".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("80000000"),
+ sort_key: (3, "unreachable"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.6666666666666666,
+ positions: vec![],
+ string: "unreachable!(…)".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "unreachable!(…)"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.47619047619047616,
+ positions: vec![],
+ string: "unreachable_unchecked".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("80000000"),
+ sort_key: (3, "unreachable_unchecked"),
+ },
+ ];
+ CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::default());
+ assert_eq!(
+ matches[0].string_match.string.as_str(),
+ "unreachable!(…)",
+ "Match order not expected"
+ );
+
+ // Case 5: "unreachable"
let query: Option<&str> = Some("unreachable");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
@@ -956,17 +1000,17 @@ fn test_sort_matches_jsx_event_handler(_cx: &mut TestAppContext) {
.collect::<Vec<&str>>(),
vec![
"onAbort?",
+ "onAuxClick?",
"onAbortCapture?",
"onAnimationEnd?",
- "onAnimationEndCapture?",
- "onAnimationIteration?",
"onAnimationStart?",
- "onAuxClick?",
"onAuxClickCapture?",
- "onCanPlay?",
- "onChange?",
+ "onAnimationIteration?",
+ "onAnimationEndCapture?",
"onDrag?",
- "onDragEnd?",
+ "onLoad?",
+ "onPlay?",
+ "onPaste?",
]
);
}
@@ -985,7 +1029,7 @@ fn test_sort_matches_for_snippets(_cx: &mut TestAppContext) {
},
is_snippet: false,
sort_text: Some("80000000"),
- sort_key: (2, "unreachable"),
+ sort_key: (2, "println"),
},
SortableMatch {
string_match: StringMatch {
@@ -1142,16 +1186,148 @@ fn test_sort_matches_for_exact_match(_cx: &mut TestAppContext) {
.collect::<Vec<&str>>(),
vec![
"set_text",
- "set_context_menu_options",
- "set_placeholder_text",
"set_text_style_refinement",
- "select_to_end_of_excerpt",
- "select_to_end_of_previous_excerpt",
- "select_to_next_subword_end",
+ "set_placeholder_text",
+ "set_context_menu_options",
+ "set_custom_context_menu",
"select_to_next_word_end",
+ "select_to_next_subword_end",
+ "select_to_end_of_excerpt",
"select_to_start_of_excerpt",
"select_to_start_of_next_excerpt",
- "set_custom_context_menu"
+ "select_to_end_of_previous_excerpt",
+ ]
+ );
+}
+
+#[gpui::test]
+fn test_sort_matches_for_prefix_matches(_cx: &mut TestAppContext) {
+ // Case 1: "set"
+ let query: Option<&str> = Some("set");
+ let mut matches: Vec<SortableMatch<'_>> = vec![
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.12631578947368421,
+ positions: vec![],
+ string: "select_to_beginning".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_to_beginning"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.15000000000000002,
+ positions: vec![],
+ string: "set_collapse_matches".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "set_collapse_matches"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.21428571428571427,
+ positions: vec![],
+ string: "set_autoindent".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "set_autoindent"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.11538461538461539,
+ positions: vec![],
+ string: "set_all_diagnostics_active".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "set_all_diagnostics_active"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.1142857142857143,
+ positions: vec![],
+ string: "select_to_end_of_line".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_to_end_of_line"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.15000000000000002,
+ positions: vec![],
+ string: "select_all".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_all"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.13636363636363635,
+ positions: vec![],
+ string: "select_line".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_line"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.13636363636363635,
+ positions: vec![],
+ string: "select_left".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_left"),
+ },
+ SortableMatch {
+ string_match: StringMatch {
+ candidate_id: 0,
+ score: 0.13636363636363635,
+ positions: vec![],
+ string: "select_down".to_string(),
+ },
+ is_snippet: false,
+ sort_text: Some("7fffffff"),
+ sort_key: (3, "select_down"),
+ },
+ ];
+ CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
+ println!(
+ "{:?}",
+ matches
+ .iter()
+ .map(|m| m.string_match.string.as_str())
+ .collect::<Vec<&str>>(),
+ );
+ assert_eq!(
+ matches
+ .iter()
+ .map(|m| m.string_match.string.as_str())
+ .collect::<Vec<&str>>(),
+ vec![
+ "set_autoindent",
+ "set_collapse_matches",
+ "set_all_diagnostics_active",
+ "select_all",
+ "select_down",
+ "select_left",
+ "select_line",
+ "select_to_beginning",
+ "select_to_end_of_line",
]
);
}
@@ -673,9 +673,10 @@ impl CompletionsMenu {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum MatchTier<'a> {
WordStartMatch {
- sort_bucket: Reverse<i32>,
+ sort_prefix: Reverse<usize>,
sort_snippet: Reverse<i32>,
sort_text: Option<&'a str>,
+ sort_score: Reverse<OrderedFloat<f64>>,
sort_key: (usize, &'a str),
},
OtherMatch {
@@ -685,10 +686,6 @@ impl CompletionsMenu {
// Our goal here is to intelligently sort completion suggestions. We want to
// balance the raw fuzzy match score with hints from the language server
- //
- // We first primary sort using fuzzy score by putting matches into multiple
- // buckets. Among these buckets matches are then compared by
- // various criteria like snippet, LSP hints, kind, label text etc.
let query_start_lower = query
.and_then(|q| q.chars().next())
@@ -696,8 +693,9 @@ impl CompletionsMenu {
matches.sort_unstable_by_key(|mat| {
let score = mat.string_match.score;
+ let sort_score = Reverse(OrderedFloat(score));
- let is_other_match = query_start_lower
+ let query_start_doesnt_match_split_words = query_start_lower
.map(|query_char| {
!split_words(&mat.string_match.string).any(|word| {
word.chars()
@@ -708,26 +706,38 @@ impl CompletionsMenu {
})
.unwrap_or(false);
- if is_other_match {
- let sort_score = Reverse(OrderedFloat(score));
+ if query_start_doesnt_match_split_words {
MatchTier::OtherMatch { sort_score }
} else {
- // Convert fuzzy match score (0.0-1.0) to a priority bucket (0-3)
- let sort_bucket = Reverse(match (score * 10.0).floor() as i32 {
- s if s >= 7 => 3,
- s if s >= 1 => 2,
- s if s > 0 => 1,
- _ => 0,
- });
let sort_snippet = match snippet_sort_order {
SnippetSortOrder::Top => Reverse(if mat.is_snippet { 1 } else { 0 }),
SnippetSortOrder::Bottom => Reverse(if mat.is_snippet { 0 } else { 1 }),
SnippetSortOrder::Inline => Reverse(0),
};
+ let mixed_case_prefix_length = Reverse(
+ query
+ .map(|q| {
+ q.chars()
+ .zip(mat.string_match.string.chars())
+ .enumerate()
+ .take_while(|(i, (q_char, match_char))| {
+ if *i == 0 {
+ // Case-sensitive comparison for first character
+ q_char == match_char
+ } else {
+ // Case-insensitive comparison for other characters
+ q_char.to_lowercase().eq(match_char.to_lowercase())
+ }
+ })
+ .count()
+ })
+ .unwrap_or(0),
+ );
MatchTier::WordStartMatch {
- sort_bucket,
+ sort_prefix: mixed_case_prefix_length,
sort_snippet,
sort_text: mat.sort_text,
+ sort_score,
sort_key: mat.sort_key,
}
}