diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-29 12:19:31 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-29 12:19:31 +0000 |
commit | ef1177c5b5a7ced9866025a51c10e4375e2a37fd (patch) | |
tree | 347f2a22132be0106d4703526288665b6d33a136 | |
parent | 7b246a6a14a8170f99dd5992f7d9dd4347722a69 (diff) | |
parent | 77b4a1c5ef594ddd78c77dd8bb05fba14b99cc9f (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.rs | 11 | ||||
-rw-r--r-- | crates/hir_def/src/import_map.rs | 207 | ||||
-rw-r--r-- | crates/ide_db/src/imports_locator.rs | 19 |
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)] | ||
243 | pub 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)] |
242 | pub struct Query { | 254 | pub 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 { | |||
251 | impl Query { | 264 | impl 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 | ||
306 | fn 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); |