editor: Improve code completion filtering to provide fewer and more accurate suggestions (#32928)
Closes #32756 - Uses `filter_text` from LSP source to filter items in completion list. This fixes noisy lists like on typing `await` in Rust, it would suggest `await.or`, `await.and`, etc., which are bad suggestions. Fallbacks to label. - Add `penalize_length` flag to fuzzy matcher, which was the default behavior across. Now, this flag is set to `false` just for code completion fuzzy matching. This fixes the case where if the query is `unreac` and the completion items are `unreachable` and `unreachable!()`, the item with a shorter length would have a larger score than the other one, which is not right in the case of auto-complete context. Now these two items will have the same fuzzy score, and LSP `sort_text` will take over in finalizing its ranking. - Updated test to be more utility based rather than example based. This will help to iterate/verify logic faster on what's going on. Before/After: await: <img width="600" alt="before-await" src="https://github.com/user-attachments/assets/384138dd-a90d-4942-a430-6ae15df37268" /> <img width="600" alt="after-await" src="https://github.com/user-attachments/assets/d05a10fa-bae5-49bd-9fe7-9933ff215f29" /> iter: <img width="600" alt="before-iter" src="https://github.com/user-attachments/assets/6e57ffe9-007d-4b17-9cc2-d48fc0176c8e" /> <img width="600" alt="after-iter" src="https://github.com/user-attachments/assets/a8577a9f-dcc8-4fd6-9ba0-b7590584ec31" /> opt: <img width="600" alt="opt-before" src="https://github.com/user-attachments/assets/d45b6c52-c9ee-4bf3-8552-d5e3fdbecbff" /> <img width="600" alt="opt-after" src="https://github.com/user-attachments/assets/daac11a8-9699-48f8-b441-19fe9803848d" /> Release Notes: - Improved code completion filtering to provide fewer and more accurate suggestions.
This commit is contained in:
parent
65067dad9e
commit
131f2857a5
45 changed files with 303 additions and 430 deletions
|
@ -260,7 +260,7 @@ impl CompletionsMenu {
|
|||
let match_candidates = completions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.filter_text()))
|
||||
.map(|(id, completion)| StringMatchCandidate::new(id, completion.filter_text()))
|
||||
.collect();
|
||||
|
||||
let completions_menu = Self {
|
||||
|
@ -979,7 +979,8 @@ impl CompletionsMenu {
|
|||
&match_candidates,
|
||||
&query,
|
||||
query.chars().any(|c| c.is_uppercase()),
|
||||
100,
|
||||
false,
|
||||
1000,
|
||||
&cancel_filter,
|
||||
background_executor,
|
||||
)
|
||||
|
@ -1055,13 +1056,12 @@ impl CompletionsMenu {
|
|||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum MatchTier<'a> {
|
||||
WordStartMatch {
|
||||
sort_capitalize: Reverse<usize>,
|
||||
sort_exact: Reverse<i32>,
|
||||
sort_positions: Vec<usize>,
|
||||
sort_snippet: Reverse<i32>,
|
||||
sort_kind: usize,
|
||||
sort_fuzzy_bracket: Reverse<usize>,
|
||||
sort_text: Option<&'a str>,
|
||||
sort_score: Reverse<OrderedFloat<f64>>,
|
||||
sort_text: Option<&'a str>,
|
||||
sort_kind: usize,
|
||||
sort_label: &'a str,
|
||||
},
|
||||
OtherMatch {
|
||||
|
@ -1069,14 +1069,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
|
||||
|
||||
// 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.score).fold(0.0, f64::max);
|
||||
let fuzzy_bracket_threshold = max_score * (3.0 / 5.0);
|
||||
|
||||
let query_start_lower = query
|
||||
.as_ref()
|
||||
.and_then(|q| q.chars().next())
|
||||
|
@ -1117,34 +1109,25 @@ impl CompletionsMenu {
|
|||
if query_start_doesnt_match_split_words {
|
||||
MatchTier::OtherMatch { sort_score }
|
||||
} else {
|
||||
let sort_fuzzy_bracket = Reverse(if score >= fuzzy_bracket_threshold {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
});
|
||||
let sort_snippet = match snippet_sort_order {
|
||||
SnippetSortOrder::Top => Reverse(if is_snippet { 1 } else { 0 }),
|
||||
SnippetSortOrder::Bottom => Reverse(if is_snippet { 0 } else { 1 }),
|
||||
SnippetSortOrder::Inline => Reverse(0),
|
||||
};
|
||||
let sort_capitalize = Reverse(
|
||||
query
|
||||
.as_ref()
|
||||
.and_then(|q| q.chars().next())
|
||||
.zip(string_match.string.chars().next())
|
||||
.map(|(q_char, s_char)| if q_char == s_char { 1 } else { 0 })
|
||||
.unwrap_or(0),
|
||||
);
|
||||
let sort_positions = string_match.positions.clone();
|
||||
let sort_exact = Reverse(if Some(completion.filter_text()) == query {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
});
|
||||
|
||||
MatchTier::WordStartMatch {
|
||||
sort_capitalize,
|
||||
sort_exact,
|
||||
sort_positions,
|
||||
sort_snippet,
|
||||
sort_kind,
|
||||
sort_fuzzy_bracket,
|
||||
sort_text,
|
||||
sort_score,
|
||||
sort_text,
|
||||
sort_kind,
|
||||
sort_label,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue