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 = 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::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, 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 { - 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; }