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

Smit Barmase created

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.

Change summary

crates/editor/src/code_completion_tests.rs | 685 +++++++++++++++++++++++
crates/editor/src/code_context_menus.rs    |  17 
2 files changed, 693 insertions(+), 9 deletions(-)

Detailed changes

crates/editor/src/code_completion_tests.rs 🔗

@@ -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"
+    );
+}

crates/editor/src/code_context_menus.rs 🔗

@@ -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,