editor: Use fuzzy crate in code completions tests instead of hard coded values (#32565)

This PR makes it a lot cleaner to write code completion tests. It
doesn't contain any logical changes, just refactoring.

Before, we used to depend on hard-coded values of fuzzy score and its
positions for tests. Now we don't need them, as fuzzy crate will handle
that for us. This is possible because fuzzy match score isn't dependent
on relative candidates or the number of candidates; rather, it's just a
one-to-one mapping for each candidate and its score.

This also makes it test robust for future purposes if there are changes
in fuzzy score logic.

Before:
```rs
  SortableMatch {
            string_match: StringMatch {  // -> whole struct provided by fuzzy crate
                candidate_id: 1115,
                score: 1.0,
                positions: vec![],
                string: "Item".to_string(),
            },
            is_snippet: false,  // -> changed to snippet kind
            sort_text: Some("16"),
            sort_kind: 3, // -> changed to function, constant, variable kind
            sort_label: "Item",
        },
```

After:
```rs
  CompletionBuilder::function("Item", "16")
```

Release Notes:

- N/A
This commit is contained in:
Smit Barmase 2025-06-11 23:26:19 +05:30 committed by GitHub
parent 7f150f7e0f
commit 65a1d09d24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 394 additions and 2340 deletions

File diff suppressed because it is too large Load diff

View file

@ -1044,55 +1044,14 @@ impl CompletionsMenu {
self.handle_selection_changed(provider.as_deref(), window, cx);
}
fn sort_string_matches(
pub fn sort_string_matches(
matches: Vec<StringMatch>,
query: Option<&str>,
snippet_sort_order: SnippetSortOrder,
completions: &[Completion],
) -> Vec<StringMatch> {
let mut sortable_items: Vec<SortableMatch<'_>> = matches
.into_iter()
.map(|string_match| {
let completion = &completions[string_match.candidate_id];
let mut matches = matches;
let is_snippet = matches!(
&completion.source,
CompletionSource::Lsp { lsp_completion, .. }
if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
);
let sort_text =
if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source {
lsp_completion.sort_text.as_deref()
} else {
None
};
let (sort_kind, sort_label) = completion.sort_key();
SortableMatch {
string_match,
is_snippet,
sort_text,
sort_kind,
sort_label,
}
})
.collect();
Self::sort_matches(&mut sortable_items, query, snippet_sort_order);
sortable_items
.into_iter()
.map(|sortable| sortable.string_match)
.collect()
}
pub fn sort_matches(
matches: &mut Vec<SortableMatch<'_>>,
query: Option<&str>,
snippet_sort_order: SnippetSortOrder,
) {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum MatchTier<'a> {
WordStartMatch {
@ -1114,10 +1073,7 @@ impl CompletionsMenu {
// In a fuzzy bracket, matches with a score of 1.0 are prioritized.
// The remaining matches are partitioned into two groups at 3/5 of the max_score.
let max_score = matches
.iter()
.map(|mat| mat.string_match.score)
.fold(0.0, f64::max);
let max_score = matches.iter().map(|mat| mat.score).fold(0.0, f64::max);
let fuzzy_bracket_threshold = max_score * (3.0 / 5.0);
let query_start_lower = query
@ -1125,13 +1081,30 @@ impl CompletionsMenu {
.and_then(|q| q.chars().next())
.and_then(|c| c.to_lowercase().next());
matches.sort_unstable_by_key(|mat| {
let score = mat.string_match.score;
matches.sort_unstable_by_key(|string_match| {
let completion = &completions[string_match.candidate_id];
let is_snippet = matches!(
&completion.source,
CompletionSource::Lsp { lsp_completion, .. }
if lsp_completion.kind == Some(CompletionItemKind::SNIPPET)
);
let sort_text = if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source
{
lsp_completion.sort_text.as_deref()
} else {
None
};
let (sort_kind, sort_label) = completion.sort_key();
let score = string_match.score;
let sort_score = Reverse(OrderedFloat(score));
let query_start_doesnt_match_split_words = query_start_lower
.map(|query_char| {
!split_words(&mat.string_match.string).any(|word| {
!split_words(&string_match.string).any(|word| {
word.chars()
.next()
.and_then(|c| c.to_lowercase().next())
@ -1149,8 +1122,8 @@ impl CompletionsMenu {
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::Top => Reverse(if is_snippet { 1 } else { 0 }),
SnippetSortOrder::Bottom => Reverse(if is_snippet { 0 } else { 1 }),
SnippetSortOrder::Inline => Reverse(0),
};
let sort_mixed_case_prefix_length = Reverse(
@ -1158,7 +1131,7 @@ impl CompletionsMenu {
.as_ref()
.map(|q| {
q.chars()
.zip(mat.string_match.string.chars())
.zip(string_match.string.chars())
.enumerate()
.take_while(|(i, (q_char, match_char))| {
if *i == 0 {
@ -1176,14 +1149,16 @@ impl CompletionsMenu {
MatchTier::WordStartMatch {
sort_mixed_case_prefix_length,
sort_snippet,
sort_kind: mat.sort_kind,
sort_kind,
sort_fuzzy_bracket,
sort_text: mat.sort_text,
sort_text,
sort_score,
sort_label: mat.sort_label,
sort_label,
}
}
});
matches
}
pub fn preserve_markdown_cache(&mut self, prev_menu: CompletionsMenu) {
@ -1215,15 +1190,6 @@ impl CompletionsMenu {
}
}
#[derive(Debug)]
pub struct SortableMatch<'a> {
pub string_match: StringMatch,
pub is_snippet: bool,
pub sort_text: Option<&'a str>,
pub sort_kind: usize,
pub sort_label: &'a str,
}
#[derive(Clone)]
pub struct AvailableCodeAction {
pub excerpt_id: ExcerptId,