diff options
-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); |