aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-29 12:19:31 +0000
committerGitHub <[email protected]>2020-12-29 12:19:31 +0000
commitef1177c5b5a7ced9866025a51c10e4375e2a37fd (patch)
tree347f2a22132be0106d4703526288665b6d33a136
parent7b246a6a14a8170f99dd5992f7d9dd4347722a69 (diff)
parent77b4a1c5ef594ddd78c77dd8bb05fba14b99cc9f (diff)
Merge #7064
7064: Ignore qualifiers when doing autoimport completions lookup r=lnicola a=SomeoneToIgnore A follow-up of https://github.com/rust-analyzer/rust-analyzer/pull/6918#issuecomment-748511151 and the PR itself. Tweaks the `import_map` query api to be more flexible with the ways to match against the import path and now fuzzy imports search in names only. This had improved the completion speed for me locally in ~5 times for `fuzzy_completion` span time, but please recheck me here. IMO we're fast and presice enough now, so I've added the modules back to the fuzzy search output. Also tweaks the the expect tests to display functions explicitly, to avoid confusing "duplicate" results. Co-authored-by: Kirill Bulatov <[email protected]>
-rw-r--r--crates/completion/src/completions/unqualified_path.rs11
-rw-r--r--crates/hir_def/src/import_map.rs207
-rw-r--r--crates/ide_db/src/imports_locator.rs19
3 files changed, 188 insertions, 49 deletions
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index d09849752..59f950189 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -101,8 +101,9 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
101// 101//
102// .Fuzzy search details 102// .Fuzzy search details
103// 103//
104// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only 104// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
105// (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers. 105// (i.e. in `HashMap` in the `std::collections::HashMap` path).
106// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
106// 107//
107// .Merge Behavior 108// .Merge Behavior
108// 109//
@@ -126,6 +127,10 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
126 let _p = profile::span("fuzzy_completion"); 127 let _p = profile::span("fuzzy_completion");
127 let potential_import_name = ctx.token.to_string(); 128 let potential_import_name = ctx.token.to_string();
128 129
130 if potential_import_name.len() < 2 {
131 return None;
132 }
133
129 let current_module = ctx.scope.module()?; 134 let current_module = ctx.scope.module()?;
130 let anchor = ctx.name_ref_syntax.as_ref()?; 135 let anchor = ctx.name_ref_syntax.as_ref()?;
131 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; 136 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
@@ -133,7 +138,7 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
133 let mut all_mod_paths = imports_locator::find_similar_imports( 138 let mut all_mod_paths = imports_locator::find_similar_imports(
134 &ctx.sema, 139 &ctx.sema,
135 ctx.krate?, 140 ctx.krate?,
136 Some(100), 141 Some(40),
137 &potential_import_name, 142 &potential_import_name,
138 true, 143 true,
139 ) 144 )
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index c0f108848..fdc681d6c 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -238,11 +238,24 @@ pub enum ImportKind {
238 BuiltinType, 238 BuiltinType,
239} 239}
240 240
241/// A way to match import map contents against the search query.
242#[derive(Debug)]
243pub enum SearchMode {
244 /// Import map entry should strictly match the query string.
245 Equals,
246 /// Import map entry should contain the query string.
247 Contains,
248 /// Import map entry should contain all letters from the query string,
249 /// in the same order, but not necessary adjacent.
250 Fuzzy,
251}
252
241#[derive(Debug)] 253#[derive(Debug)]
242pub struct Query { 254pub struct Query {
243 query: String, 255 query: String,
244 lowercased: String, 256 lowercased: String,
245 anchor_end: bool, 257 name_only: bool,
258 search_mode: SearchMode,
246 case_sensitive: bool, 259 case_sensitive: bool,
247 limit: usize, 260 limit: usize,
248 exclude_import_kinds: FxHashSet<ImportKind>, 261 exclude_import_kinds: FxHashSet<ImportKind>,
@@ -251,19 +264,26 @@ pub struct Query {
251impl Query { 264impl Query {
252 pub fn new(query: &str) -> Self { 265 pub fn new(query: &str) -> Self {
253 Self { 266 Self {
254 lowercased: query.to_lowercase(),
255 query: query.to_string(), 267 query: query.to_string(),
256 anchor_end: false, 268 lowercased: query.to_lowercase(),
269 name_only: false,
270 search_mode: SearchMode::Contains,
257 case_sensitive: false, 271 case_sensitive: false,
258 limit: usize::max_value(), 272 limit: usize::max_value(),
259 exclude_import_kinds: FxHashSet::default(), 273 exclude_import_kinds: FxHashSet::default(),
260 } 274 }
261 } 275 }
262 276
263 /// Only returns items whose paths end with the (case-insensitive) query string as their last 277 /// Matches entries' names only, ignoring the rest of
264 /// segment. 278 /// the qualifier.
265 pub fn anchor_end(self) -> Self { 279 /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
266 Self { anchor_end: true, ..self } 280 pub fn name_only(self) -> Self {
281 Self { name_only: true, ..self }
282 }
283
284 /// Specifies the way to search for the entries using the query.
285 pub fn search_mode(self, search_mode: SearchMode) -> Self {
286 Self { search_mode, ..self }
267 } 287 }
268 288
269 /// Limits the returned number of items to `limit`. 289 /// Limits the returned number of items to `limit`.
@@ -283,6 +303,40 @@ impl Query {
283 } 303 }
284} 304}
285 305
306fn contains_query(query: &Query, input_path: &ImportPath, enforce_lowercase: bool) -> bool {
307 let mut input = if query.name_only {
308 input_path.segments.last().unwrap().to_string()
309 } else {
310 input_path.to_string()
311 };
312 if enforce_lowercase || !query.case_sensitive {
313 input.make_ascii_lowercase();
314 }
315
316 let query_string =
317 if !enforce_lowercase && query.case_sensitive { &query.query } else { &query.lowercased };
318
319 match query.search_mode {
320 SearchMode::Equals => &input == query_string,
321 SearchMode::Contains => input.contains(query_string),
322 SearchMode::Fuzzy => {
323 let mut unchecked_query_chars = query_string.chars();
324 let mut mismatching_query_char = unchecked_query_chars.next();
325
326 for input_char in input.chars() {
327 match mismatching_query_char {
328 None => return true,
329 Some(matching_query_char) if matching_query_char == input_char => {
330 mismatching_query_char = unchecked_query_chars.next();
331 }
332 _ => (),
333 }
334 }
335 mismatching_query_char.is_none()
336 }
337 }
338}
339
286/// Searches dependencies of `krate` for an importable path matching `query`. 340/// Searches dependencies of `krate` for an importable path matching `query`.
287/// 341///
288/// This returns a list of items that could be imported from dependencies of `krate`. 342/// This returns a list of items that could be imported from dependencies of `krate`.
@@ -312,39 +366,29 @@ pub fn search_dependencies<'a>(
312 let importables = &import_map.importables[indexed_value.value as usize..]; 366 let importables = &import_map.importables[indexed_value.value as usize..];
313 367
314 // Path shared by the importable items in this group. 368 // Path shared by the importable items in this group.
315 let path = &import_map.map[&importables[0]].path; 369 let common_importables_path = &import_map.map[&importables[0]].path;
316 370 if !contains_query(&query, common_importables_path, true) {
317 if query.anchor_end { 371 continue;
318 // Last segment must match query.
319 let last = path.segments.last().unwrap().to_string();
320 if last.to_lowercase() != query.lowercased {
321 continue;
322 }
323 } 372 }
324 373
374 let common_importables_path_fst = fst_path(common_importables_path);
325 // Add the items from this `ModPath` group. Those are all subsequent items in 375 // Add the items from this `ModPath` group. Those are all subsequent items in
326 // `importables` whose paths match `path`. 376 // `importables` whose paths match `path`.
327 let iter = importables 377 let iter = importables
328 .iter() 378 .iter()
329 .copied() 379 .copied()
330 .take_while(|item| { 380 .take_while(|item| {
331 let item_path = &import_map.map[item].path; 381 common_importables_path_fst == fst_path(&import_map.map[item].path)
332 fst_path(item_path) == fst_path(path)
333 }) 382 })
334 .filter(|&item| match item_import_kind(item) { 383 .filter(|&item| match item_import_kind(item) {
335 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind), 384 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
336 None => true, 385 None => true,
386 })
387 .filter(|item| {
388 !query.case_sensitive // we've already checked the common importables path case-insensitively
389 || contains_query(&query, &import_map.map[item].path, false)
337 }); 390 });
338 391 res.extend(iter);
339 if query.case_sensitive {
340 // FIXME: This does not do a subsequence match.
341 res.extend(iter.filter(|item| {
342 let item_path = &import_map.map[item].path;
343 item_path.to_string().contains(&query.query)
344 }));
345 } else {
346 res.extend(iter);
347 }
348 392
349 if res.len() >= query.limit { 393 if res.len() >= query.limit {
350 res.truncate(query.limit); 394 res.truncate(query.limit);
@@ -388,7 +432,7 @@ mod tests {
388 use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; 432 use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
389 use expect_test::{expect, Expect}; 433 use expect_test::{expect, Expect};
390 434
391 use crate::{test_db::TestDB, AssocContainerId, Lookup}; 435 use crate::{data::FunctionData, test_db::TestDB, AssocContainerId, Lookup};
392 436
393 use super::*; 437 use super::*;
394 438
@@ -407,14 +451,31 @@ mod tests {
407 .into_iter() 451 .into_iter()
408 .filter_map(|item| { 452 .filter_map(|item| {
409 let mark = match item { 453 let mark = match item {
454 ItemInNs::Types(ModuleDefId::FunctionId(_))
455 | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
410 ItemInNs::Types(_) => "t", 456 ItemInNs::Types(_) => "t",
411 ItemInNs::Values(_) => "v", 457 ItemInNs::Values(_) => "v",
412 ItemInNs::Macros(_) => "m", 458 ItemInNs::Macros(_) => "m",
413 }; 459 };
414 let item = assoc_to_trait(&db, item);
415 item.krate(db.upcast()).map(|krate| { 460 item.krate(db.upcast()).map(|krate| {
416 let map = db.import_map(krate); 461 let map = db.import_map(krate);
417 let path = map.path_of(item).unwrap(); 462
463 let path = match assoc_to_trait(&db, item) {
464 Some(trait_) => {
465 let mut full_path = map.path_of(trait_).unwrap().to_string();
466 if let ItemInNs::Types(ModuleDefId::FunctionId(function_id))
467 | ItemInNs::Values(ModuleDefId::FunctionId(function_id)) = item
468 {
469 full_path += &format!(
470 "::{}",
471 FunctionData::fn_data_query(&db, function_id).name
472 );
473 }
474 full_path
475 }
476 None => map.path_of(item).unwrap().to_string(),
477 };
478
418 format!( 479 format!(
419 "{}::{} ({})\n", 480 "{}::{} ({})\n",
420 crate_graph[krate].display_name.as_ref().unwrap(), 481 crate_graph[krate].display_name.as_ref().unwrap(),
@@ -427,15 +488,15 @@ mod tests {
427 expect.assert_eq(&actual) 488 expect.assert_eq(&actual)
428 } 489 }
429 490
430 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs { 491 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
431 let assoc: AssocItemId = match item { 492 let assoc: AssocItemId = match item {
432 ItemInNs::Types(it) | ItemInNs::Values(it) => match it { 493 ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
433 ModuleDefId::TypeAliasId(it) => it.into(), 494 ModuleDefId::TypeAliasId(it) => it.into(),
434 ModuleDefId::FunctionId(it) => it.into(), 495 ModuleDefId::FunctionId(it) => it.into(),
435 ModuleDefId::ConstId(it) => it.into(), 496 ModuleDefId::ConstId(it) => it.into(),
436 _ => return item, 497 _ => return None,
437 }, 498 },
438 _ => return item, 499 _ => return None,
439 }; 500 };
440 501
441 let container = match assoc { 502 let container = match assoc {
@@ -445,8 +506,8 @@ mod tests {
445 }; 506 };
446 507
447 match container { 508 match container {
448 AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()), 509 AssocContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
449 _ => item, 510 _ => None,
450 } 511 }
451 } 512 }
452 513
@@ -685,7 +746,7 @@ mod tests {
685 } 746 }
686 747
687 #[test] 748 #[test]
688 fn search() { 749 fn search_mode() {
689 let ra_fixture = r#" 750 let ra_fixture = r#"
690 //- /main.rs crate:main deps:dep 751 //- /main.rs crate:main deps:dep
691 //- /dep.rs crate:dep deps:tdep 752 //- /dep.rs crate:dep deps:tdep
@@ -713,28 +774,96 @@ mod tests {
713 check_search( 774 check_search(
714 ra_fixture, 775 ra_fixture,
715 "main", 776 "main",
716 Query::new("fmt"), 777 Query::new("fmt").search_mode(SearchMode::Fuzzy),
717 expect![[r#" 778 expect![[r#"
718 dep::fmt (t) 779 dep::fmt (t)
719 dep::Fmt (t) 780 dep::Fmt (t)
720 dep::Fmt (v) 781 dep::Fmt (v)
721 dep::Fmt (m) 782 dep::Fmt (m)
722 dep::fmt::Display (t) 783 dep::fmt::Display (t)
723 dep::format (v) 784 dep::format (f)
785 dep::fmt::Display::fmt (f)
786 "#]],
787 );
788
789 check_search(
790 ra_fixture,
791 "main",
792 Query::new("fmt").search_mode(SearchMode::Equals),
793 expect![[r#"
794 dep::fmt (t)
795 dep::Fmt (t)
796 dep::Fmt (v)
797 dep::Fmt (m)
798 dep::fmt::Display::fmt (f)
799 "#]],
800 );
801
802 check_search(
803 ra_fixture,
804 "main",
805 Query::new("fmt").search_mode(SearchMode::Contains),
806 expect![[r#"
807 dep::fmt (t)
808 dep::Fmt (t)
809 dep::Fmt (v)
810 dep::Fmt (m)
724 dep::fmt::Display (t) 811 dep::fmt::Display (t)
812 dep::fmt::Display::fmt (f)
725 "#]], 813 "#]],
726 ); 814 );
815 }
816
817 #[test]
818 fn name_only() {
819 let ra_fixture = r#"
820 //- /main.rs crate:main deps:dep
821 //- /dep.rs crate:dep deps:tdep
822 use tdep::fmt as fmt_dep;
823 pub mod fmt {
824 pub trait Display {
825 fn fmt();
826 }
827 }
828 #[macro_export]
829 macro_rules! Fmt {
830 () => {};
831 }
832 pub struct Fmt;
833
834 pub fn format() {}
835 pub fn no() {}
836
837 //- /tdep.rs crate:tdep
838 pub mod fmt {
839 pub struct NotImportableFromMain;
840 }
841 "#;
727 842
728 check_search( 843 check_search(
729 ra_fixture, 844 ra_fixture,
730 "main", 845 "main",
731 Query::new("fmt").anchor_end(), 846 Query::new("fmt"),
732 expect![[r#" 847 expect![[r#"
733 dep::fmt (t) 848 dep::fmt (t)
734 dep::Fmt (t) 849 dep::Fmt (t)
735 dep::Fmt (v) 850 dep::Fmt (v)
736 dep::Fmt (m) 851 dep::Fmt (m)
737 dep::fmt::Display (t) 852 dep::fmt::Display (t)
853 dep::fmt::Display::fmt (f)
854 "#]],
855 );
856
857 check_search(
858 ra_fixture,
859 "main",
860 Query::new("fmt").name_only(),
861 expect![[r#"
862 dep::fmt (t)
863 dep::Fmt (t)
864 dep::Fmt (v)
865 dep::Fmt (m)
866 dep::fmt::Display::fmt (f)
738 "#]], 867 "#]],
739 ); 868 );
740 } 869 }
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index b2980a5d6..b6355af4b 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -27,7 +27,11 @@ pub fn find_exact_imports<'a>(
27 local_query.limit(40); 27 local_query.limit(40);
28 local_query 28 local_query
29 }, 29 },
30 import_map::Query::new(name_to_import).anchor_end().case_sensitive().limit(40), 30 import_map::Query::new(name_to_import)
31 .limit(40)
32 .name_only()
33 .search_mode(import_map::SearchMode::Equals)
34 .case_sensitive(),
31 ) 35 )
32} 36}
33 37
@@ -35,17 +39,18 @@ pub fn find_similar_imports<'a>(
35 sema: &Semantics<'a, RootDatabase>, 39 sema: &Semantics<'a, RootDatabase>,
36 krate: Crate, 40 krate: Crate,
37 limit: Option<usize>, 41 limit: Option<usize>,
38 name_to_import: &str, 42 fuzzy_search_string: &str,
39 ignore_modules: bool, 43 name_only: bool,
40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 44) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
41 let _p = profile::span("find_similar_imports"); 45 let _p = profile::span("find_similar_imports");
42 46
43 let mut external_query = import_map::Query::new(name_to_import); 47 let mut external_query =
44 if ignore_modules { 48 import_map::Query::new(fuzzy_search_string).search_mode(import_map::SearchMode::Fuzzy);
45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module); 49 if name_only {
50 external_query = external_query.name_only();
46 } 51 }
47 52
48 let mut local_query = symbol_index::Query::new(name_to_import.to_string()); 53 let mut local_query = symbol_index::Query::new(fuzzy_search_string.to_string());
49 54
50 if let Some(limit) = limit { 55 if let Some(limit) = limit {
51 local_query.limit(limit); 56 local_query.limit(limit);