editor: Handle more completions sort cases in Rust and Python (#29926)

Closes #29725

Adds 3 more tests for Rust `into` and `await` cases, and Python
`__init__` case. Tweaks sort logic to accommodate them.

Release Notes:

- Improved code completion sort order, handling more cases with Rust and
Python.
This commit is contained in:
Smit Barmase 2025-05-05 20:48:52 +05:30 committed by GitHub
parent b0414df921
commit 07b4480396
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 693 additions and 9 deletions

View file

@ -517,8 +517,8 @@ fn test_sort_matches_for_unreachable(_cx: &mut TestAppContext) {
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::default());
assert_eq!(
matches[0].string_match.string.as_str(),
"unreachable!(…)",
"Match order not expected"
"unreachable",
"Perfect fuzzy match should be preferred over others"
);
}
@ -1306,13 +1306,6 @@ fn test_sort_matches_for_prefix_matches(_cx: &mut TestAppContext) {
},
];
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()
@ -1331,3 +1324,677 @@ fn test_sort_matches_for_prefix_matches(_cx: &mut TestAppContext) {
]
);
}
#[gpui::test]
fn test_sort_matches_for_await(_cx: &mut TestAppContext) {
// Case 1: "awa"
let query: Option<&str> = Some("awa");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.6000000000000001,
positions: vec![],
string: "await".to_string(),
},
is_snippet: false,
sort_text: Some("7fffffff"),
sort_key: (0, "await"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 35,
score: 0.375,
positions: vec![],
string: "await.ne".to_string(),
},
is_snippet: false,
sort_text: Some("80000010"),
sort_key: (3, "await.ne"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 34,
score: 0.375,
positions: vec![],
string: "await.eq".to_string(),
},
is_snippet: false,
sort_text: Some("80000010"),
sort_key: (3, "await.eq"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 18,
score: 0.375,
positions: vec![],
string: "await.or".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.or"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 21,
score: 0.3333333333333333,
positions: vec![],
string: "await.zip".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.zip"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 20,
score: 0.3333333333333333,
positions: vec![],
string: "await.xor".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.xor"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 15,
score: 0.3333333333333333,
positions: vec![],
string: "await.and".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.and"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 9,
score: 0.3333333333333333,
positions: vec![],
string: "await.map".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.map"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 47,
score: 0.30000000000000004,
positions: vec![],
string: "await.take".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.take"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"await",
"await.or",
"await.xor",
"await.take",
"await.and",
"await.map",
"await.zip",
"await.eq",
"await.ne"
]
);
// Case 2: "await"
let query: Option<&str> = Some("await");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 1.0,
positions: vec![],
string: "await".to_string(),
},
is_snippet: false,
sort_text: Some("7fffffff"),
sort_key: (0, "await"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 35,
score: 0.625,
positions: vec![],
string: "await.ne".to_string(),
},
is_snippet: false,
sort_text: Some("80000010"),
sort_key: (3, "await.ne"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 34,
score: 0.625,
positions: vec![],
string: "await.eq".to_string(),
},
is_snippet: false,
sort_text: Some("80000010"),
sort_key: (3, "await.eq"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 18,
score: 0.625,
positions: vec![],
string: "await.or".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.or"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 21,
score: 0.5555555555555556,
positions: vec![],
string: "await.zip".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.zip"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 20,
score: 0.5555555555555556,
positions: vec![],
string: "await.xor".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.xor"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 15,
score: 0.5555555555555556,
positions: vec![],
string: "await.and".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.and"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 9,
score: 0.5555555555555556,
positions: vec![],
string: "await.map".to_string(),
},
is_snippet: false,
sort_text: Some("80000006"),
sort_key: (3, "await.map"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 47,
score: 0.5,
positions: vec![],
string: "await.take".to_string(),
},
is_snippet: false,
sort_text: Some("7ffffff8"),
sort_key: (3, "await.take"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"await",
"await.or",
"await.xor",
"await.take",
"await.and",
"await.map",
"await.zip",
"await.eq",
"await.ne"
]
);
}
#[gpui::test]
fn test_sort_matches_for_python_init(_cx: &mut TestAppContext) {
// Case 1: "__in"
let query: Option<&str> = Some("__in");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 211,
score: 0.5,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0003.__init__"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.5,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0003"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 215,
score: 0.23529411764705882,
positions: vec![],
string: "__instancecheck__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0005.__instancecheck__"),
sort_key: (3, "__instancecheck__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 213,
score: 0.23529411764705882,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0004.__init_subclass__"),
sort_key: (3, "__init_subclass__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 4,
score: 0.23529411764705882,
positions: vec![],
string: "__instancecheck__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0005"),
sort_key: (3, "__instancecheck__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 2,
score: 0.23529411764705882,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0004"),
sort_key: (3, "__init_subclass__"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"__init__",
"__init__",
"__init_subclass__",
"__init_subclass__",
"__instancecheck__",
"__instancecheck__",
]
);
// Case 2: "__ini"
let query: Option<&str> = Some("__ini");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 9,
score: 0.625,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0004.__init__"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.625,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0004"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 10,
score: 0.29411764705882354,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0003.__init_subclass__"),
sort_key: (3, "__init_subclass__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 1,
score: 0.29411764705882354,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0003"),
sort_key: (3, "__init_subclass__"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"__init__",
"__init__",
"__init_subclass__",
"__init_subclass__",
]
);
// Case 3: "__init"
let query: Option<&str> = Some("__init");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 7,
score: 0.75,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0000.__init__"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.75,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0000"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 8,
score: 0.3529411764705882,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0001.__init_subclass__"),
sort_key: (3, "__init_subclass__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 1,
score: 0.3529411764705882,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0001"),
sort_key: (3, "__init_subclass__"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"__init__",
"__init__",
"__init_subclass__",
"__init_subclass__",
]
);
// Case 4: "__init_"
let query: Option<&str> = Some("__init_");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 4,
score: 0.875,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("11.9999.__init__"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.875,
positions: vec![],
string: "__init__".to_string(),
},
is_snippet: false,
sort_text: Some("11.9999"),
sort_key: (3, "__init__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 5,
score: 0.4117647058823529,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0000.__init_subclass__"),
sort_key: (3, "__init_subclass__"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 1,
score: 0.4117647058823529,
positions: vec![],
string: "__init_subclass__".to_string(),
},
is_snippet: false,
sort_text: Some("05.0000"),
sort_key: (3, "__init_subclass__"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::Top);
assert_eq!(
matches
.iter()
.map(|m| m.string_match.string.as_str())
.collect::<Vec<&str>>(),
vec![
"__init__",
"__init__",
"__init_subclass__",
"__init_subclass__",
]
);
}
#[gpui::test]
fn test_sort_matches_for_rust_into(_cx: &mut TestAppContext) {
// Case 1: "int"
let query: Option<&str> = Some("int");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 67,
score: 0.75,
positions: vec![],
string: "into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 68,
score: 0.30000000000000004,
positions: vec![],
string: "try_into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "try_into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 108,
score: 0.2571428571428571,
positions: vec![],
string: "println".to_string(),
},
is_snippet: true,
sort_text: Some("80000004"),
sort_key: (3, "println"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 73,
score: 0.24,
positions: vec![],
string: "clone_into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "clone_into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 1,
score: 0.23076923076923078,
positions: vec![],
string: "into_searcher".to_string(),
},
is_snippet: false,
sort_text: Some("80000000"),
sort_key: (3, "into_searcher"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 109,
score: 0.22499999999999998,
positions: vec![],
string: "eprintln".to_string(),
},
is_snippet: true,
sort_text: Some("80000004"),
sort_key: (3, "eprintln"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::default());
assert_eq!(
matches[0].string_match.string.as_str(),
"into",
"Match order not expected"
);
// Case 2: "into"
let query: Option<&str> = Some("into");
let mut matches: Vec<SortableMatch<'_>> = vec![
SortableMatch {
string_match: StringMatch {
candidate_id: 65,
score: 1.0,
positions: vec![],
string: "into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 66,
score: 0.4,
positions: vec![],
string: "try_into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "try_into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 71,
score: 0.32,
positions: vec![],
string: "clone_into".to_string(),
},
is_snippet: false,
sort_text: Some("80000004"),
sort_key: (3, "clone_into"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 0,
score: 0.3076923076923077,
positions: vec![],
string: "into_searcher".to_string(),
},
is_snippet: false,
sort_text: Some("80000000"),
sort_key: (3, "into_searcher"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 27,
score: 0.09,
positions: vec![],
string: "split_terminator".to_string(),
},
is_snippet: false,
sort_text: Some("7fffffff"),
sort_key: (3, "split_terminator"),
},
SortableMatch {
string_match: StringMatch {
candidate_id: 28,
score: 0.08470588235294117,
positions: vec![],
string: "rsplit_terminator".to_string(),
},
is_snippet: false,
sort_text: Some("7fffffff"),
sort_key: (3, "rsplit_terminator"),
},
];
CompletionsMenu::sort_matches(&mut matches, query, SnippetSortOrder::default());
assert_eq!(
matches[0].string_match.string.as_str(),
"into",
"Match order not expected"
);
}

View file

@ -674,6 +674,7 @@ impl CompletionsMenu {
enum MatchTier<'a> {
WordStartMatch {
sort_prefix: Reverse<usize>,
sort_fuzzy_bracket: Reverse<usize>,
sort_snippet: Reverse<i32>,
sort_text: Option<&'a str>,
sort_score: Reverse<OrderedFloat<f64>>,
@ -687,6 +688,14 @@ 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
// In a fuzzy bracket, matches with a score of 1.0 are prioritized.
// The remaining matches are partitioned into two groups at 2/3 of the max_score.
let max_score = matches
.iter()
.map(|mat| mat.string_match.score)
.fold(0.0, f64::max);
let second_bracket_threshold = max_score * (2.0 / 3.0);
let query_start_lower = query
.and_then(|q| q.chars().next())
.and_then(|c| c.to_lowercase().next());
@ -709,6 +718,13 @@ impl CompletionsMenu {
if query_start_doesnt_match_split_words {
MatchTier::OtherMatch { sort_score }
} else {
let sort_fuzzy_bracket = Reverse(if score == 1.0 {
2
} else if score >= second_bracket_threshold {
1
} else {
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 }),
@ -735,6 +751,7 @@ impl CompletionsMenu {
);
MatchTier::WordStartMatch {
sort_prefix: mixed_case_prefix_length,
sort_fuzzy_bracket,
sort_snippet,
sort_text: mat.sort_text,
sort_score,