aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);