about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukas.wirth@ferrous-systems.com>2023-10-05 13:21:12 +0200
committerLukas Wirth <lukas.wirth@ferrous-systems.com>2023-10-05 13:21:12 +0200
commit4af730eb26fc8fc41e0fd8952e8b04759910b580 (patch)
tree0161106468342ea8512c7c819f9ddd9c577e33a9
parent695c612489d54aa81f9817076819273bdc8aa64b (diff)
downloadrust-4af730eb26fc8fc41e0fd8952e8b04759910b580.tar.gz
rust-4af730eb26fc8fc41e0fd8952e8b04759910b580.zip
Do flyimport completions by prefix search for short paths
-rw-r--r--crates/hir-def/src/import_map.rs13
-rw-r--r--crates/ide-completion/src/completions/flyimport.rs12
-rw-r--r--crates/ide-completion/src/tests/flyimport.rs76
-rw-r--r--crates/ide-db/src/imports/import_assets.rs47
-rw-r--r--crates/ide-db/src/items_locator.rs30
-rw-r--r--crates/ide-db/src/symbol_index.rs43
6 files changed, 174 insertions, 47 deletions
diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs
index 90763d4c3df..6461439bb71 100644
--- a/crates/hir-def/src/import_map.rs
+++ b/crates/hir-def/src/import_map.rs
@@ -283,6 +283,8 @@ enum SearchMode {
     /// Import map entry should contain all letters from the query string,
     /// in the same order, but not necessary adjacent.
     Fuzzy,
+    /// Import map entry should match the query string by prefix.
+    Prefix,
 }
 
 /// Three possible ways to search for the name in associated and/or other items.
@@ -324,6 +326,14 @@ impl Query {
         Self { search_mode: SearchMode::Fuzzy, ..self }
     }
 
+    pub fn prefix(self) -> Self {
+        Self { search_mode: SearchMode::Prefix, ..self }
+    }
+
+    pub fn exact(self) -> Self {
+        Self { search_mode: SearchMode::Exact, ..self }
+    }
+
     /// Specifies whether we want to include associated items in the result.
     pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
         Self { assoc_mode, ..self }
@@ -361,7 +371,8 @@ impl Query {
         let query_string = if case_insensitive { &self.lowercased } else { &self.query };
 
         match self.search_mode {
-            SearchMode::Exact => &input == query_string,
+            SearchMode::Exact => input == *query_string,
+            SearchMode::Prefix => input.starts_with(query_string),
             SearchMode::Fuzzy => {
                 let mut input_chars = input.chars();
                 for query_char in query_string.chars() {
diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs
index 39c1b7f7b3f..0961021e48e 100644
--- a/crates/ide-completion/src/completions/flyimport.rs
+++ b/crates/ide-completion/src/completions/flyimport.rs
@@ -13,10 +13,9 @@ use crate::{
         TypeLocation,
     },
     render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
+    Completions,
 };
 
-use super::Completions;
-
 // Feature: Completion With Autoimport
 //
 // When completing names in the current scope, proposes additional imports from other modules or crates,
@@ -377,9 +376,12 @@ fn import_assets_for_path(
         &ctx.sema,
         ctx.token.parent()?,
     )?;
-    if fuzzy_name_length < 3 {
-        cov_mark::hit!(flyimport_exact_on_short_path);
-        assets_for_path.path_fuzzy_name_to_exact(false);
+    if fuzzy_name_length == 0 {
+        // nothing matches the empty string exactly, but we still compute assoc items in this case
+        assets_for_path.path_fuzzy_name_to_exact();
+    } else if fuzzy_name_length < 3 {
+        cov_mark::hit!(flyimport_prefix_on_short_path);
+        assets_for_path.path_fuzzy_name_to_prefix();
     }
     Some(assets_for_path)
 }
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs
index 4cdfd546f6a..21f693d79f1 100644
--- a/crates/ide-completion/src/tests/flyimport.rs
+++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -116,19 +116,47 @@ fn main() {
 }
 
 #[test]
-fn short_paths_are_ignored() {
-    cov_mark::check!(flyimport_exact_on_short_path);
+fn short_paths_are_prefix_matched() {
+    cov_mark::check!(flyimport_prefix_on_short_path);
 
     check(
         r#"
 //- /lib.rs crate:dep
-pub struct Bar;
+pub struct Barc;
 pub struct Rcar;
 pub struct Rc;
+pub const RC: () = ();
 pub mod some_module {
     pub struct Bar;
     pub struct Rcar;
     pub struct Rc;
+    pub const RC: () = ();
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    Rc$0
+}
+"#,
+        expect![[r#"
+            st Rc (use dep::Rc)
+            st Rcar (use dep::Rcar)
+            st Rc (use dep::some_module::Rc)
+            st Rcar (use dep::some_module::Rcar)
+        "#]],
+    );
+    check(
+        r#"
+//- /lib.rs crate:dep
+pub struct Barc;
+pub struct Rcar;
+pub struct Rc;
+pub const RC: () = ();
+pub mod some_module {
+    pub struct Bar;
+    pub struct Rcar;
+    pub struct Rc;
+    pub const RC: () = ();
 }
 
 //- /main.rs crate:main deps:dep
@@ -137,8 +165,36 @@ fn main() {
 }
 "#,
         expect![[r#"
+            ct RC (use dep::RC)
             st Rc (use dep::Rc)
+            st Rcar (use dep::Rcar)
+            ct RC (use dep::some_module::RC)
             st Rc (use dep::some_module::Rc)
+            st Rcar (use dep::some_module::Rcar)
+        "#]],
+    );
+    check(
+        r#"
+//- /lib.rs crate:dep
+pub struct Barc;
+pub struct Rcar;
+pub struct Rc;
+pub const RC: () = ();
+pub mod some_module {
+    pub struct Bar;
+    pub struct Rcar;
+    pub struct Rc;
+    pub const RC: () = ();
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+    RC$0
+}
+"#,
+        expect![[r#"
+            ct RC (use dep::RC)
+            ct RC (use dep::some_module::RC)
         "#]],
     );
 }
@@ -841,8 +897,8 @@ fn main() {
     TES$0
 }"#,
         expect![[r#"
-        ct TEST_CONST (use foo::TEST_CONST)
-    "#]],
+            ct TEST_CONST (use foo::TEST_CONST)
+        "#]],
     );
 
     check(
@@ -858,9 +914,9 @@ fn main() {
     tes$0
 }"#,
         expect![[r#"
-        ct TEST_CONST (use foo::TEST_CONST)
-        fn test_function() (use foo::test_function) fn() -> i32
-    "#]],
+            ct TEST_CONST (use foo::TEST_CONST)
+            fn test_function() (use foo::test_function) fn() -> i32
+        "#]],
     );
 
     check(
@@ -873,9 +929,9 @@ mod foo {
 }
 
 fn main() {
-    Te$0
+    Tes$0
 }"#,
-        expect![[]],
+        expect![""],
     );
 }
 
diff --git a/crates/ide-db/src/imports/import_assets.rs b/crates/ide-db/src/imports/import_assets.rs
index e475c5cd66b..da5a951f0b7 100644
--- a/crates/ide-db/src/imports/import_assets.rs
+++ b/crates/ide-db/src/imports/import_assets.rs
@@ -68,22 +68,29 @@ pub struct FirstSegmentUnresolved {
 pub enum NameToImport {
     /// Requires items with names that exactly match the given string, bool indicates case-sensitivity.
     Exact(String, bool),
-    /// Requires items with names that case-insensitively contain all letters from the string,
+    /// Requires items with names that match the given string by prefix, bool indicates case-sensitivity.
+    Prefix(String, bool),
+    /// Requires items with names contain all letters from the string,
     /// in the same order, but not necessary adjacent.
-    Fuzzy(String),
+    Fuzzy(String, bool),
 }
 
 impl NameToImport {
     pub fn exact_case_sensitive(s: String) -> NameToImport {
         NameToImport::Exact(s, true)
     }
-}
 
-impl NameToImport {
+    pub fn fuzzy(s: String) -> NameToImport {
+        // unless all chars are lowercase, we do a case sensitive search
+        let case_sensitive = s.chars().any(|c| c.is_uppercase());
+        NameToImport::Fuzzy(s, case_sensitive)
+    }
+
     pub fn text(&self) -> &str {
         match self {
-            NameToImport::Exact(text, _) => text.as_str(),
-            NameToImport::Fuzzy(text) => text.as_str(),
+            NameToImport::Prefix(text, _)
+            | NameToImport::Exact(text, _)
+            | NameToImport::Fuzzy(text, _) => text.as_str(),
         }
     }
 }
@@ -165,7 +172,7 @@ impl ImportAssets {
         Some(Self {
             import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate {
                 receiver_ty,
-                assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name),
+                assoc_item_name: NameToImport::fuzzy(fuzzy_method_name),
             }),
             module_with_candidate: module_with_method_call,
             candidate_node,
@@ -228,12 +235,30 @@ impl ImportAssets {
         self.search_for(sema, None, prefer_no_std)
     }
 
-    pub fn path_fuzzy_name_to_exact(&mut self, case_sensitive: bool) {
+    /// Requires imports to by prefix instead of fuzzily.
+    pub fn path_fuzzy_name_to_prefix(&mut self) {
+        if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
+            &mut self.import_candidate
+        {
+            let (name, case_sensitive) = match to_import {
+                NameToImport::Fuzzy(name, case_sensitive) => {
+                    (std::mem::take(name), *case_sensitive)
+                }
+                _ => return,
+            };
+            *to_import = NameToImport::Prefix(name, case_sensitive);
+        }
+    }
+
+    /// Requires imports to match exactly instead of fuzzily.
+    pub fn path_fuzzy_name_to_exact(&mut self) {
         if let ImportCandidate::Path(PathImportCandidate { name: to_import, .. }) =
             &mut self.import_candidate
         {
-            let name = match to_import {
-                NameToImport::Fuzzy(name) => std::mem::take(name),
+            let (name, case_sensitive) = match to_import {
+                NameToImport::Fuzzy(name, case_sensitive) => {
+                    (std::mem::take(name), *case_sensitive)
+                }
                 _ => return,
             };
             *to_import = NameToImport::Exact(name, case_sensitive);
@@ -623,7 +648,7 @@ impl ImportCandidate {
         fuzzy_name: String,
         sema: &Semantics<'_, RootDatabase>,
     ) -> Option<Self> {
-        path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
+        path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name))
     }
 }
 
diff --git a/crates/ide-db/src/items_locator.rs b/crates/ide-db/src/items_locator.rs
index 3f7a3ec2d0f..67ed44f08b7 100644
--- a/crates/ide-db/src/items_locator.rs
+++ b/crates/ide-db/src/items_locator.rs
@@ -31,26 +31,34 @@ pub fn items_with_name<'a>(
         )
     });
 
+    let prefix = matches!(name, NameToImport::Prefix(..));
     let (mut local_query, mut external_query) = match name {
-        NameToImport::Exact(exact_name, case_sensitive) => {
+        NameToImport::Prefix(exact_name, case_sensitive)
+        | NameToImport::Exact(exact_name, case_sensitive) => {
             let mut local_query = symbol_index::Query::new(exact_name.clone());
-            local_query.exact();
-
-            let external_query = import_map::Query::new(exact_name);
-
-            (
-                local_query,
-                if case_sensitive { external_query.case_sensitive() } else { external_query },
-            )
+            let mut external_query = import_map::Query::new(exact_name);
+            if prefix {
+                local_query.prefix();
+                external_query = external_query.prefix();
+            } else {
+                local_query.exact();
+                external_query = external_query.exact();
+            }
+            if case_sensitive {
+                local_query.case_sensitive();
+                external_query = external_query.case_sensitive();
+            }
+            (local_query, external_query)
         }
-        NameToImport::Fuzzy(fuzzy_search_string) => {
+        NameToImport::Fuzzy(fuzzy_search_string, case_sensitive) => {
             let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
+            local_query.fuzzy();
 
             let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
                 .fuzzy()
                 .assoc_search_mode(assoc_item_search);
 
-            if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
+            if case_sensitive {
                 local_query.case_sensitive();
                 external_query = external_query.case_sensitive();
             }
diff --git a/crates/ide-db/src/symbol_index.rs b/crates/ide-db/src/symbol_index.rs
index f699f999baf..3e89159c2c6 100644
--- a/crates/ide-db/src/symbol_index.rs
+++ b/crates/ide-db/src/symbol_index.rs
@@ -43,13 +43,20 @@ use triomphe::Arc;
 
 use crate::RootDatabase;
 
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum SearchMode {
+    Fuzzy,
+    Exact,
+    Prefix,
+}
+
 #[derive(Debug)]
 pub struct Query {
     query: String,
     lowercased: String,
     only_types: bool,
     libs: bool,
-    exact: bool,
+    mode: SearchMode,
     case_sensitive: bool,
     limit: usize,
 }
@@ -62,7 +69,7 @@ impl Query {
             lowercased,
             only_types: false,
             libs: false,
-            exact: false,
+            mode: SearchMode::Fuzzy,
             case_sensitive: false,
             limit: usize::max_value(),
         }
@@ -76,8 +83,16 @@ impl Query {
         self.libs = true;
     }
 
+    pub fn fuzzy(&mut self) {
+        self.mode = SearchMode::Fuzzy;
+    }
+
     pub fn exact(&mut self) {
-        self.exact = true;
+        self.mode = SearchMode::Exact;
+    }
+
+    pub fn prefix(&mut self) {
+        self.mode = SearchMode::Prefix;
     }
 
     pub fn case_sensitive(&mut self) {
@@ -329,13 +344,23 @@ impl Query {
                     {
                         continue;
                     }
-                    if self.exact {
-                        if symbol.name != self.query {
-                            continue;
+                    let skip = match self.mode {
+                        SearchMode::Fuzzy => {
+                            self.case_sensitive
+                                && self.query.chars().any(|c| !symbol.name.contains(c))
                         }
-                    } else if self.case_sensitive
-                        && self.query.chars().any(|c| !symbol.name.contains(c))
-                    {
+                        SearchMode::Exact => symbol.name != self.query,
+                        SearchMode::Prefix if self.case_sensitive => {
+                            !symbol.name.starts_with(&self.query)
+                        }
+                        SearchMode::Prefix => symbol
+                            .name
+                            .chars()
+                            .zip(self.lowercased.chars())
+                            .all(|(n, q)| n.to_lowercase().next() == Some(q)),
+                    };
+
+                    if skip {
                         continue;
                     }