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
|
@ -214,6 +214,7 @@ fn search(
|
||||||
&entry_candidates,
|
&entry_candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Arc::new(AtomicBool::default()),
|
&Arc::new(AtomicBool::default()),
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -307,6 +307,7 @@ pub(crate) fn search_symbols(
|
||||||
&visible_match_candidates,
|
&visible_match_candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
MAX_MATCHES,
|
MAX_MATCHES,
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
@ -315,6 +316,7 @@ pub(crate) fn search_symbols(
|
||||||
&external_match_candidates,
|
&external_match_candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -342,6 +342,7 @@ pub(crate) fn search_threads(
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -224,6 +224,7 @@ impl ThreadHistory {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
MAX_MATCHES,
|
MAX_MATCHES,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -745,6 +745,7 @@ impl ContextStore {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -310,6 +310,7 @@ impl ModelMatcher {
|
||||||
&self.candidates,
|
&self.candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
self.bg_executor.clone(),
|
self.bg_executor.clone(),
|
||||||
|
|
|
@ -62,6 +62,7 @@ impl SlashCommandCompletionProvider {
|
||||||
&candidates,
|
&candidates,
|
||||||
&command_name,
|
&command_name,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -147,6 +147,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
|
||||||
&Options::match_candidates_for_args(),
|
&Options::match_candidates_for_args(),
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
10,
|
10,
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -261,6 +261,7 @@ fn tab_items_for_queries(
|
||||||
&match_candidates,
|
&match_candidates,
|
||||||
query,
|
query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&cancel,
|
&cancel,
|
||||||
background_executor.clone(),
|
background_executor.clone(),
|
||||||
|
|
|
@ -293,6 +293,7 @@ impl MessageEditor {
|
||||||
candidates,
|
candidates,
|
||||||
query,
|
query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -499,6 +499,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -542,6 +543,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -593,6 +595,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -623,6 +626,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -699,6 +703,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -734,6 +739,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -758,6 +764,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
@ -791,6 +798,7 @@ impl CollabPanel {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
|
|
@ -295,6 +295,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||||
&self.match_candidates,
|
&self.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -327,6 +327,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
10000,
|
10000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -183,6 +183,7 @@ impl PickerDelegate for AttachModalDelegate {
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -1279,6 +1279,7 @@ impl PickerDelegate for DebugDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
1000,
|
1000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -527,6 +527,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||||
&string_matches,
|
&string_matches,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{code_context_menus::CompletionsMenu, editor_settings::SnippetSortOrder};
|
use crate::{code_context_menus::CompletionsMenu, editor_settings::SnippetSortOrder};
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use language::CodeLabel;
|
use language::CodeLabel;
|
||||||
use lsp::{CompletionItem, CompletionItemKind, LanguageServerId};
|
use lsp::{CompletionItem, CompletionItemKind, LanguageServerId};
|
||||||
|
@ -9,432 +9,258 @@ use std::sync::atomic::AtomicBool;
|
||||||
use text::Anchor;
|
use text::Anchor;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_local_variable_over_global_variable(cx: &mut TestAppContext) {
|
async fn test_sort_kind(cx: &mut TestAppContext) {
|
||||||
// Case 1: "foo"
|
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
|
CompletionBuilder::function("floorf128", None, "80000000"),
|
||||||
CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
|
CompletionBuilder::constant("foo_bar_baz", None, "80000000"),
|
||||||
CompletionBuilder::constant("floorf64", "80000000"),
|
CompletionBuilder::variable("foo_bar_qux", None, "80000000"),
|
||||||
CompletionBuilder::constant("floorf32", "80000000"),
|
|
||||||
CompletionBuilder::constant("floorf16", "80000000"),
|
|
||||||
CompletionBuilder::constant("floorf128", "80000000"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("foo", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "foo_bar_qux");
|
filter_and_sort_matches("foo", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
assert_eq!(matches[1], "foo_bar_baz");
|
|
||||||
assert_eq!(matches[2], "floorf16");
|
|
||||||
assert_eq!(matches[3], "floorf32");
|
|
||||||
|
|
||||||
// Case 2: "foobar"
|
// variable takes precedence over constant
|
||||||
let completions = vec![
|
// constant take precedence over function
|
||||||
CompletionBuilder::constant("foo_bar_baz", "7fffffff"),
|
assert_eq!(
|
||||||
CompletionBuilder::variable("foo_bar_qux", "7ffffffe"),
|
matches
|
||||||
];
|
.iter()
|
||||||
let matches = sort_matches("foobar", &completions, SnippetSortOrder::default(), cx).await;
|
.map(|m| m.string.as_str())
|
||||||
assert_eq!(matches[0], "foo_bar_qux");
|
.collect::<Vec<_>>(),
|
||||||
assert_eq!(matches[1], "foo_bar_baz");
|
vec!["foo_bar_qux", "foo_bar_baz", "floorf128"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// fuzzy score should match for first two items as query is common prefix
|
||||||
|
assert_eq!(matches[0].score, matches[1].score);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_local_variable_over_global_enum(cx: &mut TestAppContext) {
|
async fn test_fuzzy_score(cx: &mut TestAppContext) {
|
||||||
// Case 1: "ele"
|
// first character sensitive over sort_text and sort_kind
|
||||||
|
{
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::constant("ElementType", "7fffffff"),
|
CompletionBuilder::variable("element_type", None, "7ffffffe"),
|
||||||
CompletionBuilder::variable("element_type", "7ffffffe"),
|
CompletionBuilder::constant("ElementType", None, "7fffffff"),
|
||||||
CompletionBuilder::constant("simd_select", "80000000"),
|
|
||||||
CompletionBuilder::keyword("while let", "7fffffff"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("ele", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "element_type");
|
filter_and_sort_matches("Elem", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
assert_eq!(matches[1], "ElementType");
|
assert_eq!(
|
||||||
|
matches
|
||||||
// Case 2: "eleme"
|
.iter()
|
||||||
let completions = vec![
|
.map(|m| m.string.as_str())
|
||||||
CompletionBuilder::constant("ElementType", "7fffffff"),
|
.collect::<Vec<_>>(),
|
||||||
CompletionBuilder::variable("element_type", "7ffffffe"),
|
vec!["ElementType", "element_type"]
|
||||||
CompletionBuilder::constant("REPLACEMENT_CHARACTER", "80000000"),
|
);
|
||||||
];
|
assert!(matches[0].score > matches[1].score);
|
||||||
let matches = sort_matches("eleme", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "element_type");
|
|
||||||
assert_eq!(matches[1], "ElementType");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
// fuzzy takes over sort_text and sort_kind
|
||||||
async fn test_sort_matches_capitalization(cx: &mut TestAppContext) {
|
{
|
||||||
// Case 1: "Elem"
|
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::constant("ElementType", "7fffffff"),
|
CompletionBuilder::function("onAbort?", None, "12"),
|
||||||
CompletionBuilder::variable("element_type", "7ffffffe"),
|
CompletionBuilder::function("onAuxClick?", None, "12"),
|
||||||
|
CompletionBuilder::variable("onPlay?", None, "12"),
|
||||||
|
CompletionBuilder::variable("onLoad?", None, "12"),
|
||||||
|
CompletionBuilder::variable("onDrag?", None, "12"),
|
||||||
|
CompletionBuilder::function("onPause?", None, "10"),
|
||||||
|
CompletionBuilder::function("onPaste?", None, "10"),
|
||||||
|
CompletionBuilder::function("onAnimationEnd?", None, "12"),
|
||||||
|
CompletionBuilder::function("onAbortCapture?", None, "12"),
|
||||||
|
CompletionBuilder::constant("onChange?", None, "12"),
|
||||||
|
CompletionBuilder::constant("onWaiting?", None, "12"),
|
||||||
|
CompletionBuilder::function("onCanPlay?", None, "12"),
|
||||||
];
|
];
|
||||||
let matches = sort_matches("Elem", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "ElementType");
|
filter_and_sort_matches("ona", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
assert_eq!(matches[1], "element_type");
|
for i in 0..4 {
|
||||||
|
assert!(matches[i].string.to_lowercase().starts_with("ona"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
// plain fuzzy prefix match
|
||||||
async fn test_sort_matches_for_unreachable(cx: &mut TestAppContext) {
|
{
|
||||||
// Case 1: "unre"
|
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::function("unreachable", "80000000"),
|
CompletionBuilder::function("set_text", None, "7fffffff"),
|
||||||
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
|
CompletionBuilder::function("set_placeholder_text", None, "7fffffff"),
|
||||||
CompletionBuilder::function("unchecked_rem", "80000000"),
|
CompletionBuilder::function("set_text_style_refinement", None, "7fffffff"),
|
||||||
CompletionBuilder::function("unreachable_unchecked", "80000000"),
|
CompletionBuilder::function("set_context_menu_options", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_next_word_end", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_next_subword_end", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("set_custom_context_menu", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_end_of_excerpt", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_start_of_excerpt", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_start_of_next_excerpt", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("select_to_end_of_previous_excerpt", None, "7fffffff"),
|
||||||
];
|
];
|
||||||
let matches = sort_matches("unre", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "unreachable!(…)");
|
filter_and_sort_matches("set_text", &completions, SnippetSortOrder::Top, cx).await;
|
||||||
|
assert_eq!(matches[0].string, "set_text");
|
||||||
// Case 2: "unrea"
|
assert_eq!(matches[1].string, "set_text_style_refinement");
|
||||||
let completions = vec![
|
assert_eq!(matches[2].string, "set_context_menu_options");
|
||||||
CompletionBuilder::function("unreachable", "80000000"),
|
|
||||||
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
|
|
||||||
CompletionBuilder::function("unreachable_unchecked", "80000000"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("unrea", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "unreachable!(…)");
|
|
||||||
|
|
||||||
// Case 3: "unreach"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("unreachable", "80000000"),
|
|
||||||
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
|
|
||||||
CompletionBuilder::function("unreachable_unchecked", "80000000"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("unreach", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "unreachable!(…)");
|
|
||||||
|
|
||||||
// Case 4: "unreachabl"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("unreachable", "80000000"),
|
|
||||||
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
|
|
||||||
CompletionBuilder::function("unreachable_unchecked", "80000000"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "unreachable!(…)");
|
|
||||||
|
|
||||||
// Case 5: "unreachable"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("unreachable", "80000000"),
|
|
||||||
CompletionBuilder::function("unreachable!(…)", "7fffffff"),
|
|
||||||
CompletionBuilder::function("unreachable_unchecked", "80000000"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("unreachable", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "unreachable!(…)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
// fuzzy filter text over label, sort_text and sort_kind
|
||||||
async fn test_sort_matches_variable_and_constants_over_function(cx: &mut TestAppContext) {
|
{
|
||||||
// Case 1: "var" as variable
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("var", "7fffffff"),
|
|
||||||
CompletionBuilder::variable("var", "7fffffff"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "var");
|
|
||||||
assert_eq!(matches[1], "var");
|
|
||||||
|
|
||||||
// Case 2: "var" as constant
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("var", "7fffffff"),
|
|
||||||
CompletionBuilder::constant("var", "7fffffff"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("var", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "var");
|
|
||||||
assert_eq!(matches[1], "var");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_jsx_event_handler(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "on"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("onCut?", "12"),
|
|
||||||
CompletionBuilder::function("onPlay?", "12"),
|
|
||||||
CompletionBuilder::function("color?", "12"),
|
|
||||||
CompletionBuilder::function("defaultValue?", "12"),
|
|
||||||
CompletionBuilder::function("style?", "12"),
|
|
||||||
CompletionBuilder::function("className?", "12"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("on", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "onCut?");
|
|
||||||
assert_eq!(matches[1], "onPlay?");
|
|
||||||
|
|
||||||
// Case 2: "ona"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("onAbort?", "12"),
|
|
||||||
CompletionBuilder::function("onAuxClick?", "12"),
|
|
||||||
CompletionBuilder::function("onPlay?", "12"),
|
|
||||||
CompletionBuilder::function("onLoad?", "12"),
|
|
||||||
CompletionBuilder::function("onDrag?", "12"),
|
|
||||||
CompletionBuilder::function("onPause?", "12"),
|
|
||||||
CompletionBuilder::function("onPaste?", "12"),
|
|
||||||
CompletionBuilder::function("onAnimationEnd?", "12"),
|
|
||||||
CompletionBuilder::function("onAbortCapture?", "12"),
|
|
||||||
CompletionBuilder::function("onChange?", "12"),
|
|
||||||
CompletionBuilder::function("onWaiting?", "12"),
|
|
||||||
CompletionBuilder::function("onCanPlay?", "12"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("ona", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "onAbort?");
|
|
||||||
assert_eq!(matches[1], "onAuxClick?");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_snippets(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "prin"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::constant("println", "80000000"),
|
|
||||||
CompletionBuilder::snippet("println!(…)", "80000000"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("prin", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "println!(…)");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_exact_match(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "set_text"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("set_text", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_placeholder_text", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_text_style_refinement", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_context_menu_options", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_next_word_end", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_next_subword_end", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_custom_context_menu", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_end_of_excerpt", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_start_of_excerpt", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_start_of_next_excerpt", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_end_of_previous_excerpt", "7fffffff"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("set_text", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "set_text");
|
|
||||||
assert_eq!(matches[1], "set_text_style_refinement");
|
|
||||||
assert_eq!(matches[2], "set_context_menu_options");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_prefix_matches(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "set"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("select_to_beginning", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_collapse_matches", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_autoindent", "7fffffff"),
|
|
||||||
CompletionBuilder::function("set_all_diagnostics_active", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_to_end_of_line", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_all", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_line", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_left", "7fffffff"),
|
|
||||||
CompletionBuilder::function("select_down", "7fffffff"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("set", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "set_autoindent");
|
|
||||||
assert_eq!(matches[1], "set_collapse_matches");
|
|
||||||
assert_eq!(matches[2], "set_all_diagnostics_active");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_await(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "awa"
|
// Case 1: "awa"
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::keyword("await", "7fffffff"),
|
CompletionBuilder::method("await", Some("await"), "7fffffff"),
|
||||||
CompletionBuilder::function("await.ne", "80000010"),
|
CompletionBuilder::method("await.ne", Some("ne"), "80000010"),
|
||||||
CompletionBuilder::function("await.eq", "80000010"),
|
CompletionBuilder::method("await.eq", Some("eq"), "80000010"),
|
||||||
CompletionBuilder::function("await.or", "7ffffff8"),
|
CompletionBuilder::method("await.or", Some("or"), "7ffffff8"),
|
||||||
CompletionBuilder::function("await.zip", "80000006"),
|
CompletionBuilder::method("await.zip", Some("zip"), "80000006"),
|
||||||
CompletionBuilder::function("await.xor", "7ffffff8"),
|
CompletionBuilder::method("await.xor", Some("xor"), "7ffffff8"),
|
||||||
CompletionBuilder::function("await.and", "80000006"),
|
CompletionBuilder::method("await.and", Some("and"), "80000006"),
|
||||||
CompletionBuilder::function("await.map", "80000006"),
|
CompletionBuilder::method("await.map", Some("map"), "80000006"),
|
||||||
CompletionBuilder::function("await.take", "7ffffff8"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("awa", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "await");
|
|
||||||
|
|
||||||
// Case 2: "await"
|
test_for_each_prefix("await", &completions, cx, |matches| {
|
||||||
let completions = vec![
|
// for each prefix, first item should always be one with lower sort_text
|
||||||
CompletionBuilder::keyword("await", "7fffffff"),
|
assert_eq!(matches[0].string, "await");
|
||||||
CompletionBuilder::function("await.ne", "80000010"),
|
})
|
||||||
CompletionBuilder::function("await.eq", "80000010"),
|
.await;
|
||||||
CompletionBuilder::function("await.or", "7ffffff8"),
|
}
|
||||||
CompletionBuilder::function("await.zip", "80000006"),
|
|
||||||
CompletionBuilder::function("await.xor", "7ffffff8"),
|
|
||||||
CompletionBuilder::function("await.and", "80000006"),
|
|
||||||
CompletionBuilder::function("await.map", "80000006"),
|
|
||||||
CompletionBuilder::function("await.take", "7ffffff8"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("await", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "await");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_for_python_init(cx: &mut TestAppContext) {
|
async fn test_sort_text(cx: &mut TestAppContext) {
|
||||||
// Case 1: "__in"
|
// sort text takes precedance over sort_kind, when fuzzy is same
|
||||||
|
{
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::function("__init__", "05.0003.__init__"),
|
CompletionBuilder::variable("unreachable", None, "80000000"),
|
||||||
CompletionBuilder::function("__init__", "05.0003"),
|
CompletionBuilder::function("unreachable!(…)", None, "7fffffff"),
|
||||||
CompletionBuilder::function("__instancecheck__", "05.0005.__instancecheck__"),
|
CompletionBuilder::function("unchecked_rem", None, "80000010"),
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0004.__init_subclass__"),
|
CompletionBuilder::function("unreachable_unchecked", None, "80000020"),
|
||||||
CompletionBuilder::function("__instancecheck__", "05.0005"),
|
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0004"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("__in", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "__init__");
|
|
||||||
assert_eq!(matches[1], "__init__");
|
|
||||||
|
|
||||||
// Case 2: "__ini"
|
test_for_each_prefix("unreachabl", &completions, cx, |matches| {
|
||||||
let completions = vec![
|
// for each prefix, first item should always be one with lower sort_text
|
||||||
CompletionBuilder::function("__init__", "05.0004.__init__"),
|
assert_eq!(matches[0].string, "unreachable!(…)");
|
||||||
CompletionBuilder::function("__init__", "05.0004"),
|
assert_eq!(matches[1].string, "unreachable");
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0003.__init_subclass__"),
|
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0003"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("__ini", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "__init__");
|
|
||||||
assert_eq!(matches[1], "__init__");
|
|
||||||
|
|
||||||
// Case 3: "__init"
|
// fuzzy score should match for first two items as query is common prefix
|
||||||
let completions = vec![
|
assert_eq!(matches[0].score, matches[1].score);
|
||||||
CompletionBuilder::function("__init__", "05.0000.__init__"),
|
})
|
||||||
CompletionBuilder::function("__init__", "05.0000"),
|
.await;
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0001.__init_subclass__"),
|
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0001"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("__init", &completions, SnippetSortOrder::Top, cx).await;
|
|
||||||
assert_eq!(matches[0], "__init__");
|
|
||||||
assert_eq!(matches[1], "__init__");
|
|
||||||
|
|
||||||
// Case 4: "__init_"
|
let matches =
|
||||||
let completions = vec![
|
filter_and_sort_matches("unreachable", &completions, SnippetSortOrder::Top, cx).await;
|
||||||
CompletionBuilder::function("__init__", "11.9999.__init__"),
|
// exact match comes first
|
||||||
CompletionBuilder::function("__init__", "11.9999"),
|
assert_eq!(matches[0].string, "unreachable");
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0000.__init_subclass__"),
|
assert_eq!(matches[1].string, "unreachable!(…)");
|
||||||
CompletionBuilder::function("__init_subclass__", "05.0000"),
|
|
||||||
];
|
// fuzzy score should match for first two items as query is common prefix
|
||||||
let matches = sort_matches("__init_", &completions, SnippetSortOrder::Top, cx).await;
|
assert_eq!(matches[0].score, matches[1].score);
|
||||||
assert_eq!(matches[0], "__init__");
|
}
|
||||||
assert_eq!(matches[1], "__init__");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_for_rust_into(cx: &mut TestAppContext) {
|
async fn test_sort_snippet(cx: &mut TestAppContext) {
|
||||||
// Case 1: "int"
|
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::function("into", "80000004"),
|
CompletionBuilder::constant("println", None, "7fffffff"),
|
||||||
CompletionBuilder::function("try_into", "80000004"),
|
CompletionBuilder::snippet("println!(…)", None, "80000000"),
|
||||||
CompletionBuilder::snippet("println", "80000004"),
|
|
||||||
CompletionBuilder::function("clone_into", "80000004"),
|
|
||||||
CompletionBuilder::function("into_searcher", "80000000"),
|
|
||||||
CompletionBuilder::snippet("eprintln", "80000004"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("int", &completions, SnippetSortOrder::default(), cx).await;
|
let matches = filter_and_sort_matches("prin", &completions, SnippetSortOrder::Top, cx).await;
|
||||||
assert_eq!(matches[0], "into");
|
|
||||||
|
|
||||||
// Case 2: "into"
|
// snippet take precedence over sort_text and sort_kind
|
||||||
let completions = vec![
|
assert_eq!(matches[0].string, "println!(…)");
|
||||||
CompletionBuilder::function("into", "80000004"),
|
|
||||||
CompletionBuilder::function("try_into", "80000004"),
|
|
||||||
CompletionBuilder::function("clone_into", "80000004"),
|
|
||||||
CompletionBuilder::function("into_searcher", "80000000"),
|
|
||||||
CompletionBuilder::function("split_terminator", "7fffffff"),
|
|
||||||
CompletionBuilder::function("rsplit_terminator", "7fffffff"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("into", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "into");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_for_variable_over_function(cx: &mut TestAppContext) {
|
async fn test_sort_exact(cx: &mut TestAppContext) {
|
||||||
// Case 1: "serial"
|
// sort_text takes over if no exact match
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::function("serialize", "80000000"),
|
CompletionBuilder::function("into", None, "80000004"),
|
||||||
CompletionBuilder::function("serialize", "80000000"),
|
CompletionBuilder::function("try_into", None, "80000004"),
|
||||||
CompletionBuilder::variable("serialization_key", "7ffffffe"),
|
CompletionBuilder::snippet("println", None, "80000004"),
|
||||||
CompletionBuilder::function("serialize_version", "80000000"),
|
CompletionBuilder::function("clone_into", None, "80000004"),
|
||||||
CompletionBuilder::function("deserialize", "80000000"),
|
CompletionBuilder::function("into_searcher", None, "80000000"),
|
||||||
|
CompletionBuilder::snippet("eprintln", None, "80000004"),
|
||||||
];
|
];
|
||||||
let matches = sort_matches("serial", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "serialization_key");
|
filter_and_sort_matches("int", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
assert_eq!(matches[1], "serialize");
|
assert_eq!(matches[0].string, "into_searcher");
|
||||||
assert_eq!(matches[2], "serialize");
|
|
||||||
assert_eq!(matches[3], "serialize_version");
|
// exact match takes over sort_text
|
||||||
assert_eq!(matches[4], "deserialize");
|
let completions = vec![
|
||||||
|
CompletionBuilder::function("into", None, "80000004"),
|
||||||
|
CompletionBuilder::function("try_into", None, "80000004"),
|
||||||
|
CompletionBuilder::function("clone_into", None, "80000004"),
|
||||||
|
CompletionBuilder::function("into_searcher", None, "80000000"),
|
||||||
|
CompletionBuilder::function("split_terminator", None, "7fffffff"),
|
||||||
|
CompletionBuilder::function("rsplit_terminator", None, "7fffffff"),
|
||||||
|
];
|
||||||
|
let matches =
|
||||||
|
filter_and_sort_matches("into", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
|
assert_eq!(matches[0].string, "into");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_sort_matches_for_local_methods_over_library(cx: &mut TestAppContext) {
|
async fn test_sort_positions(cx: &mut TestAppContext) {
|
||||||
// Case 1: "setis"
|
// positions take precedence over fuzzy score and sort_text
|
||||||
let completions = vec![
|
let completions = vec![
|
||||||
CompletionBuilder::variable("setISODay", "16"),
|
CompletionBuilder::function("rounded-full", None, "15788"),
|
||||||
CompletionBuilder::variable("setISOWeek", "16"),
|
CompletionBuilder::variable("rounded-t-full", None, "15846"),
|
||||||
CompletionBuilder::variable("setISOWeekYear", "16"),
|
CompletionBuilder::variable("rounded-b-full", None, "15731"),
|
||||||
CompletionBuilder::function("setISOWeekYear", "16"),
|
CompletionBuilder::function("rounded-tr-full", None, "15866"),
|
||||||
CompletionBuilder::variable("setIsRefreshing", "11"),
|
|
||||||
CompletionBuilder::function("setFips", "16"),
|
|
||||||
];
|
];
|
||||||
let matches = sort_matches("setis", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "setIsRefreshing");
|
|
||||||
assert_eq!(matches[1], "setISODay");
|
|
||||||
assert_eq!(matches[2], "setISOWeek");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
let matches = filter_and_sort_matches(
|
||||||
async fn test_sort_matches_for_prioritize_not_exact_match(cx: &mut TestAppContext) {
|
|
||||||
// Case 1: "item"
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("Item", "16"),
|
|
||||||
CompletionBuilder::variable("Item", "16"),
|
|
||||||
CompletionBuilder::variable("items", "11"),
|
|
||||||
CompletionBuilder::function("ItemText", "16"),
|
|
||||||
];
|
|
||||||
let matches = sort_matches("item", &completions, SnippetSortOrder::default(), cx).await;
|
|
||||||
assert_eq!(matches[0], "items");
|
|
||||||
assert_eq!(matches[1], "Item");
|
|
||||||
assert_eq!(matches[2], "Item");
|
|
||||||
assert_eq!(matches[3], "ItemText");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_sort_matches_for_tailwind_classes(cx: &mut TestAppContext) {
|
|
||||||
let completions = vec![
|
|
||||||
CompletionBuilder::function("rounded-full", "15788"),
|
|
||||||
CompletionBuilder::variable("rounded-t-full", "15846"),
|
|
||||||
CompletionBuilder::variable("rounded-b-full", "15731"),
|
|
||||||
CompletionBuilder::function("rounded-tr-full", "15866"),
|
|
||||||
];
|
|
||||||
// Case 1: "rounded-full"
|
|
||||||
let matches = sort_matches(
|
|
||||||
"rounded-full",
|
"rounded-full",
|
||||||
&completions,
|
&completions,
|
||||||
SnippetSortOrder::default(),
|
SnippetSortOrder::default(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(matches[0], "rounded-full");
|
assert_eq!(matches[0].string, "rounded-full");
|
||||||
// Case 2: "roundedfull"
|
|
||||||
let matches = sort_matches("roundedfull", &completions, SnippetSortOrder::default(), cx).await;
|
let matches =
|
||||||
assert_eq!(matches[0], "rounded-full");
|
filter_and_sort_matches("roundedfull", &completions, SnippetSortOrder::default(), cx).await;
|
||||||
|
assert_eq!(matches[0].string, "rounded-full");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_for_each_prefix<F>(
|
||||||
|
target: &str,
|
||||||
|
completions: &Vec<Completion>,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
mut test_fn: F,
|
||||||
|
) where
|
||||||
|
F: FnMut(Vec<StringMatch>),
|
||||||
|
{
|
||||||
|
for i in 1..=target.len() {
|
||||||
|
let prefix = &target[..i];
|
||||||
|
let matches =
|
||||||
|
filter_and_sort_matches(prefix, completions, SnippetSortOrder::default(), cx).await;
|
||||||
|
test_fn(matches);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CompletionBuilder;
|
struct CompletionBuilder;
|
||||||
|
|
||||||
impl CompletionBuilder {
|
impl CompletionBuilder {
|
||||||
fn constant(label: &str, sort_text: &str) -> Completion {
|
fn constant(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||||
Self::new(label, sort_text, CompletionItemKind::CONSTANT)
|
Self::new(label, filter_text, sort_text, CompletionItemKind::CONSTANT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn function(label: &str, sort_text: &str) -> Completion {
|
fn function(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||||
Self::new(label, sort_text, CompletionItemKind::FUNCTION)
|
Self::new(label, filter_text, sort_text, CompletionItemKind::FUNCTION)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn variable(label: &str, sort_text: &str) -> Completion {
|
fn method(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||||
Self::new(label, sort_text, CompletionItemKind::VARIABLE)
|
Self::new(label, filter_text, sort_text, CompletionItemKind::METHOD)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyword(label: &str, sort_text: &str) -> Completion {
|
fn variable(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||||
Self::new(label, sort_text, CompletionItemKind::KEYWORD)
|
Self::new(label, filter_text, sort_text, CompletionItemKind::VARIABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snippet(label: &str, sort_text: &str) -> Completion {
|
fn snippet(label: &str, filter_text: Option<&str>, sort_text: &str) -> Completion {
|
||||||
Self::new(label, sort_text, CompletionItemKind::SNIPPET)
|
Self::new(label, filter_text, sort_text, CompletionItemKind::SNIPPET)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(label: &str, sort_text: &str, kind: CompletionItemKind) -> Completion {
|
fn new(
|
||||||
|
label: &str,
|
||||||
|
filter_text: Option<&str>,
|
||||||
|
sort_text: &str,
|
||||||
|
kind: CompletionItemKind,
|
||||||
|
) -> Completion {
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: Anchor::MIN..Anchor::MAX,
|
replace_range: Anchor::MIN..Anchor::MAX,
|
||||||
new_text: label.to_string(),
|
new_text: label.to_string(),
|
||||||
|
@ -451,6 +277,7 @@ impl CompletionBuilder {
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
kind: Some(kind),
|
kind: Some(kind),
|
||||||
sort_text: Some(sort_text.to_string()),
|
sort_text: Some(sort_text.to_string()),
|
||||||
|
filter_text: filter_text.map(|text| text.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
lsp_defaults: None,
|
lsp_defaults: None,
|
||||||
|
@ -463,16 +290,16 @@ impl CompletionBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sort_matches(
|
async fn filter_and_sort_matches(
|
||||||
query: &str,
|
query: &str,
|
||||||
completions: &Vec<Completion>,
|
completions: &Vec<Completion>,
|
||||||
snippet_sort_order: SnippetSortOrder,
|
snippet_sort_order: SnippetSortOrder,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> Vec<String> {
|
) -> Vec<StringMatch> {
|
||||||
let candidates: Arc<[StringMatchCandidate]> = completions
|
let candidates: Arc<[StringMatchCandidate]> = completions
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.text))
|
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.filter_text()))
|
||||||
.collect();
|
.collect();
|
||||||
let cancel_flag = Arc::new(AtomicBool::new(false));
|
let cancel_flag = Arc::new(AtomicBool::new(false));
|
||||||
let background_executor = cx.executor();
|
let background_executor = cx.executor();
|
||||||
|
@ -480,16 +307,11 @@ async fn sort_matches(
|
||||||
&candidates,
|
&candidates,
|
||||||
query,
|
query,
|
||||||
query.chars().any(|c| c.is_uppercase()),
|
query.chars().any(|c| c.is_uppercase()),
|
||||||
|
false,
|
||||||
100,
|
100,
|
||||||
&cancel_flag,
|
&cancel_flag,
|
||||||
background_executor,
|
background_executor,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let sorted_matches = CompletionsMenu::sort_string_matches(
|
CompletionsMenu::sort_string_matches(matches, Some(query), snippet_sort_order, &completions)
|
||||||
matches,
|
|
||||||
Some(query),
|
|
||||||
snippet_sort_order,
|
|
||||||
&completions,
|
|
||||||
);
|
|
||||||
sorted_matches.into_iter().map(|m| m.string).collect()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ impl CompletionsMenu {
|
||||||
let match_candidates = completions
|
let match_candidates = completions
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, completion)| StringMatchCandidate::new(id, &completion.label.filter_text()))
|
.map(|(id, completion)| StringMatchCandidate::new(id, completion.filter_text()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let completions_menu = Self {
|
let completions_menu = Self {
|
||||||
|
@ -979,7 +979,8 @@ impl CompletionsMenu {
|
||||||
&match_candidates,
|
&match_candidates,
|
||||||
&query,
|
&query,
|
||||||
query.chars().any(|c| c.is_uppercase()),
|
query.chars().any(|c| c.is_uppercase()),
|
||||||
100,
|
false,
|
||||||
|
1000,
|
||||||
&cancel_filter,
|
&cancel_filter,
|
||||||
background_executor,
|
background_executor,
|
||||||
)
|
)
|
||||||
|
@ -1055,13 +1056,12 @@ impl CompletionsMenu {
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum MatchTier<'a> {
|
enum MatchTier<'a> {
|
||||||
WordStartMatch {
|
WordStartMatch {
|
||||||
sort_capitalize: Reverse<usize>,
|
sort_exact: Reverse<i32>,
|
||||||
sort_positions: Vec<usize>,
|
sort_positions: Vec<usize>,
|
||||||
sort_snippet: Reverse<i32>,
|
sort_snippet: Reverse<i32>,
|
||||||
sort_kind: usize,
|
|
||||||
sort_fuzzy_bracket: Reverse<usize>,
|
|
||||||
sort_text: Option<&'a str>,
|
|
||||||
sort_score: Reverse<OrderedFloat<f64>>,
|
sort_score: Reverse<OrderedFloat<f64>>,
|
||||||
|
sort_text: Option<&'a str>,
|
||||||
|
sort_kind: usize,
|
||||||
sort_label: &'a str,
|
sort_label: &'a str,
|
||||||
},
|
},
|
||||||
OtherMatch {
|
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
|
let query_start_lower = query
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|q| q.chars().next())
|
.and_then(|q| q.chars().next())
|
||||||
|
@ -1117,34 +1109,25 @@ impl CompletionsMenu {
|
||||||
if query_start_doesnt_match_split_words {
|
if query_start_doesnt_match_split_words {
|
||||||
MatchTier::OtherMatch { sort_score }
|
MatchTier::OtherMatch { sort_score }
|
||||||
} else {
|
} else {
|
||||||
let sort_fuzzy_bracket = Reverse(if score >= fuzzy_bracket_threshold {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
});
|
|
||||||
let sort_snippet = match snippet_sort_order {
|
let sort_snippet = match snippet_sort_order {
|
||||||
SnippetSortOrder::Top => Reverse(if is_snippet { 1 } else { 0 }),
|
SnippetSortOrder::Top => Reverse(if is_snippet { 1 } else { 0 }),
|
||||||
SnippetSortOrder::Bottom => Reverse(if is_snippet { 0 } else { 1 }),
|
SnippetSortOrder::Bottom => Reverse(if is_snippet { 0 } else { 1 }),
|
||||||
SnippetSortOrder::Inline => Reverse(0),
|
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_positions = string_match.positions.clone();
|
||||||
|
let sort_exact = Reverse(if Some(completion.filter_text()) == query {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
});
|
||||||
|
|
||||||
MatchTier::WordStartMatch {
|
MatchTier::WordStartMatch {
|
||||||
sort_capitalize,
|
sort_exact,
|
||||||
sort_positions,
|
sort_positions,
|
||||||
sort_snippet,
|
sort_snippet,
|
||||||
sort_kind,
|
|
||||||
sort_fuzzy_bracket,
|
|
||||||
sort_text,
|
|
||||||
sort_score,
|
sort_score,
|
||||||
|
sort_text,
|
||||||
|
sort_kind,
|
||||||
sort_label,
|
sort_label,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21221,6 +21221,7 @@ fn snippet_completions(
|
||||||
&candidates,
|
&candidates,
|
||||||
&last_word,
|
&last_word,
|
||||||
last_word.chars().any(|c| c.is_uppercase()),
|
last_word.chars().any(|c| c.is_uppercase()),
|
||||||
|
true,
|
||||||
MAX_RESULTS,
|
MAX_RESULTS,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
|
|
@ -11950,7 +11950,7 @@ async fn test_completion(cx: &mut TestAppContext) {
|
||||||
.confirm_completion(&ConfirmCompletion::default(), window, cx)
|
.confirm_completion(&ConfirmCompletion::default(), window, cx)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
cx.assert_editor_state("editor.closeˇ");
|
cx.assert_editor_state("editor.clobberˇ");
|
||||||
handle_resolve_completion_request(&mut cx, None).await;
|
handle_resolve_completion_request(&mut cx, None).await;
|
||||||
apply_additional_edits.await.unwrap();
|
apply_additional_edits.await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -15468,7 +15468,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_menu_entries(&menu),
|
completion_menu_entries(&menu),
|
||||||
&["bg-red", "bg-blue", "bg-yellow"]
|
&["bg-blue", "bg-red", "bg-yellow"]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("expected completion menu to be open");
|
panic!("expected completion menu to be open");
|
||||||
|
|
|
@ -146,6 +146,7 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background_executor,
|
background_executor,
|
||||||
|
|
|
@ -474,6 +474,7 @@ impl ExtensionsPage {
|
||||||
&match_candidates,
|
&match_candidates,
|
||||||
&search,
|
&search,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
match_candidates.len(),
|
match_candidates.len(),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -418,6 +418,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||||
candidates.as_slice(),
|
candidates.as_slice(),
|
||||||
&suffix,
|
&suffix,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&cancel_flag,
|
&cancel_flag,
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct Matcher<'a> {
|
||||||
lowercase_query: &'a [char],
|
lowercase_query: &'a [char],
|
||||||
query_char_bag: CharBag,
|
query_char_bag: CharBag,
|
||||||
smart_case: bool,
|
smart_case: bool,
|
||||||
|
penalize_length: bool,
|
||||||
min_score: f64,
|
min_score: f64,
|
||||||
match_positions: Vec<usize>,
|
match_positions: Vec<usize>,
|
||||||
last_positions: Vec<usize>,
|
last_positions: Vec<usize>,
|
||||||
|
@ -35,6 +36,7 @@ impl<'a> Matcher<'a> {
|
||||||
lowercase_query: &'a [char],
|
lowercase_query: &'a [char],
|
||||||
query_char_bag: CharBag,
|
query_char_bag: CharBag,
|
||||||
smart_case: bool,
|
smart_case: bool,
|
||||||
|
penalize_length: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
query,
|
query,
|
||||||
|
@ -46,6 +48,7 @@ impl<'a> Matcher<'a> {
|
||||||
score_matrix: Vec::new(),
|
score_matrix: Vec::new(),
|
||||||
best_position_matrix: Vec::new(),
|
best_position_matrix: Vec::new(),
|
||||||
smart_case,
|
smart_case,
|
||||||
|
penalize_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +297,7 @@ impl<'a> Matcher<'a> {
|
||||||
let mut multiplier = char_score;
|
let mut multiplier = char_score;
|
||||||
|
|
||||||
// Scale the score based on how deep within the path we found the match.
|
// Scale the score based on how deep within the path we found the match.
|
||||||
if query_idx == 0 {
|
if self.penalize_length && query_idx == 0 {
|
||||||
multiplier /= ((prefix.len() + path.len()) - last_slash) as f64;
|
multiplier /= ((prefix.len() + path.len()) - last_slash) as f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,18 +358,18 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_last_positions() {
|
fn test_get_last_positions() {
|
||||||
let mut query: &[char] = &['d', 'c'];
|
let mut query: &[char] = &['d', 'c'];
|
||||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
let mut matcher = Matcher::new(query, query, query.into(), false, true);
|
||||||
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||||
assert!(!result);
|
assert!(!result);
|
||||||
|
|
||||||
query = &['c', 'd'];
|
query = &['c', 'd'];
|
||||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
let mut matcher = Matcher::new(query, query, query.into(), false, true);
|
||||||
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||||
assert!(result);
|
assert!(result);
|
||||||
assert_eq!(matcher.last_positions, vec![2, 4]);
|
assert_eq!(matcher.last_positions, vec![2, 4]);
|
||||||
|
|
||||||
query = &['z', '/', 'z', 'f'];
|
query = &['z', '/', 'z', 'f'];
|
||||||
let mut matcher = Matcher::new(query, query, query.into(), false);
|
let mut matcher = Matcher::new(query, query, query.into(), false, true);
|
||||||
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
|
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
|
||||||
assert!(result);
|
assert!(result);
|
||||||
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
|
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
|
||||||
|
@ -613,7 +616,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case);
|
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, true);
|
||||||
|
|
||||||
let cancel_flag = AtomicBool::new(false);
|
let cancel_flag = AtomicBool::new(false);
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
|
@ -95,7 +95,7 @@ pub fn match_fixed_path_set(
|
||||||
let query = query.chars().collect::<Vec<_>>();
|
let query = query.chars().collect::<Vec<_>>();
|
||||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||||
|
|
||||||
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case);
|
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case, true);
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
matcher.match_candidates(
|
matcher.match_candidates(
|
||||||
|
@ -153,7 +153,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||||
let segment_start = segment_idx * segment_size;
|
let segment_start = segment_idx * segment_size;
|
||||||
let segment_end = segment_start + segment_size;
|
let segment_end = segment_start + segment_size;
|
||||||
let mut matcher =
|
let mut matcher =
|
||||||
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
|
Matcher::new(query, lowercase_query, query_char_bag, smart_case, true);
|
||||||
|
|
||||||
let mut tree_start = 0;
|
let mut tree_start = 0;
|
||||||
for candidate_set in candidate_sets {
|
for candidate_set in candidate_sets {
|
||||||
|
|
|
@ -117,6 +117,7 @@ pub async fn match_strings<T>(
|
||||||
candidates: &[T],
|
candidates: &[T],
|
||||||
query: &str,
|
query: &str,
|
||||||
smart_case: bool,
|
smart_case: bool,
|
||||||
|
penalize_length: bool,
|
||||||
max_results: usize,
|
max_results: usize,
|
||||||
cancel_flag: &AtomicBool,
|
cancel_flag: &AtomicBool,
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
|
@ -160,8 +161,13 @@ where
|
||||||
scope.spawn(async move {
|
scope.spawn(async move {
|
||||||
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
|
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
|
||||||
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
|
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
|
||||||
let mut matcher =
|
let mut matcher = Matcher::new(
|
||||||
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
|
query,
|
||||||
|
lowercase_query,
|
||||||
|
query_char_bag,
|
||||||
|
smart_case,
|
||||||
|
penalize_length,
|
||||||
|
);
|
||||||
|
|
||||||
matcher.match_candidates(
|
matcher.match_candidates(
|
||||||
&[],
|
&[],
|
||||||
|
|
|
@ -305,6 +305,7 @@ impl PickerDelegate for BranchListDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
10000,
|
10000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -174,6 +174,7 @@ impl PickerDelegate for PickerPromptDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
10000,
|
10000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -215,6 +215,7 @@ impl IndexedDocsStore {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&AtomicBool::default(),
|
&AtomicBool::default(),
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -144,6 +144,7 @@ impl PickerDelegate for BookmarkPickerDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -122,6 +122,7 @@ impl<T> Outline<T> {
|
||||||
},
|
},
|
||||||
query,
|
query,
|
||||||
smart_case,
|
smart_case,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
|
|
@ -247,6 +247,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -3815,6 +3815,7 @@ impl OutlinePanel {
|
||||||
&generation_state.match_candidates,
|
&generation_state.match_candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
usize::MAX,
|
usize::MAX,
|
||||||
&AtomicBool::default(),
|
&AtomicBool::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -5303,6 +5303,16 @@ impl ProjectItem for Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completion {
|
impl Completion {
|
||||||
|
pub fn filter_text(&self) -> &str {
|
||||||
|
match &self.source {
|
||||||
|
CompletionSource::Lsp { lsp_completion, .. } => lsp_completion
|
||||||
|
.filter_text
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_else(|| self.label.filter_text()),
|
||||||
|
_ => self.label.filter_text(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> Option<CompletionItemKind> {
|
pub fn kind(&self) -> Option<CompletionItemKind> {
|
||||||
self.source
|
self.source
|
||||||
// `lsp::CompletionListItemDefaults` has no `kind` field
|
// `lsp::CompletionListItemDefaults` has no `kind` field
|
||||||
|
@ -5319,17 +5329,18 @@ impl Completion {
|
||||||
/// A key that can be used to sort completions when displaying
|
/// A key that can be used to sort completions when displaying
|
||||||
/// them to the user.
|
/// them to the user.
|
||||||
pub fn sort_key(&self) -> (usize, &str) {
|
pub fn sort_key(&self) -> (usize, &str) {
|
||||||
const DEFAULT_KIND_KEY: usize = 3;
|
const DEFAULT_KIND_KEY: usize = 4;
|
||||||
let kind_key = self
|
let kind_key = self
|
||||||
.kind()
|
.kind()
|
||||||
.and_then(|lsp_completion_kind| match lsp_completion_kind {
|
.and_then(|lsp_completion_kind| match lsp_completion_kind {
|
||||||
lsp::CompletionItemKind::KEYWORD => Some(0),
|
lsp::CompletionItemKind::KEYWORD => Some(0),
|
||||||
lsp::CompletionItemKind::VARIABLE => Some(1),
|
lsp::CompletionItemKind::VARIABLE => Some(1),
|
||||||
lsp::CompletionItemKind::CONSTANT => Some(2),
|
lsp::CompletionItemKind::CONSTANT => Some(2),
|
||||||
|
lsp::CompletionItemKind::PROPERTY => Some(3),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or(DEFAULT_KIND_KEY);
|
.unwrap_or(DEFAULT_KIND_KEY);
|
||||||
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
(kind_key, &self.label.filter_text())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this completion is a snippet.
|
/// Whether this completion is a snippet.
|
||||||
|
|
|
@ -66,6 +66,7 @@ impl ProjectSymbolsDelegate {
|
||||||
&self.visible_match_candidates,
|
&self.visible_match_candidates,
|
||||||
query,
|
query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
MAX_MATCHES,
|
MAX_MATCHES,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
@ -74,6 +75,7 @@ impl ProjectSymbolsDelegate {
|
||||||
&self.external_match_candidates,
|
&self.external_match_candidates,
|
||||||
query,
|
query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
MAX_MATCHES - visible_matches.len().min(MAX_MATCHES),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
@ -342,6 +344,7 @@ mod tests {
|
||||||
&candidates,
|
&candidates,
|
||||||
¶ms.query,
|
¶ms.query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
executor.clone(),
|
executor.clone(),
|
||||||
|
|
|
@ -356,6 +356,7 @@ impl PromptStore {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&cancellation_flag,
|
&cancellation_flag,
|
||||||
executor,
|
executor,
|
||||||
|
|
|
@ -251,6 +251,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||||
candidates.as_slice(),
|
candidates.as_slice(),
|
||||||
query,
|
query,
|
||||||
smart_case,
|
smart_case,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -277,6 +277,7 @@ impl PickerDelegate for ScopeSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -98,6 +98,7 @@ impl PickerDelegate for Delegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -318,6 +318,7 @@ impl TabSwitcherDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
10000,
|
10000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -358,6 +358,7 @@ impl PickerDelegate for TasksModalDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
1000,
|
1000,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
cx.background_executor().clone(),
|
cx.background_executor().clone(),
|
||||||
|
|
|
@ -244,6 +244,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -296,6 +296,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -355,6 +355,7 @@ impl PickerDelegate for ToolchainSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -147,6 +147,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||||
&candidates,
|
&candidates,
|
||||||
&query,
|
&query,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
100,
|
100,
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
background,
|
background,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue