Unnamed repository; edit this file 'description' to name the repository.
Do flyimport completions by prefix search for short paths
Lukas Wirth 2023-10-05
parent 695c612 · commit 4af730e
-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 90763d4c3d..6461439bb7 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 39c1b7f7b3..0961021e48 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 4cdfd546f6..21f693d79f 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 e475c5cd66..da5a951f0b 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 3f7a3ec2d0..67ed44f08b 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 f699f999ba..3e89159c2c 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;
}