aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/import_map.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def/src/import_map.rs')
-rw-r--r--crates/hir_def/src/import_map.rs222
1 files changed, 176 insertions, 46 deletions
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs
index c0f108848..c4dc894df 100644
--- a/crates/hir_def/src/import_map.rs
+++ b/crates/hir_def/src/import_map.rs
@@ -238,32 +238,53 @@ 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>,
249} 262}
250 263
251impl Query { 264impl Query {
252 pub fn new(query: &str) -> Self { 265 pub fn new(query: String) -> Self {
266 let lowercased = query.to_lowercase();
253 Self { 267 Self {
254 lowercased: query.to_lowercase(), 268 query,
255 query: query.to_string(), 269 lowercased,
256 anchor_end: false, 270 name_only: false,
271 search_mode: SearchMode::Contains,
257 case_sensitive: false, 272 case_sensitive: false,
258 limit: usize::max_value(), 273 limit: usize::max_value(),
259 exclude_import_kinds: FxHashSet::default(), 274 exclude_import_kinds: FxHashSet::default(),
260 } 275 }
261 } 276 }
262 277
263 /// Only returns items whose paths end with the (case-insensitive) query string as their last 278 /// Matches entries' names only, ignoring the rest of
264 /// segment. 279 /// the qualifier.
265 pub fn anchor_end(self) -> Self { 280 /// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
266 Self { anchor_end: true, ..self } 281 pub fn name_only(self) -> Self {
282 Self { name_only: true, ..self }
283 }
284
285 /// Specifies the way to search for the entries using the query.
286 pub fn search_mode(self, search_mode: SearchMode) -> Self {
287 Self { search_mode, ..self }
267 } 288 }
268 289
269 /// Limits the returned number of items to `limit`. 290 /// Limits the returned number of items to `limit`.
@@ -283,6 +304,40 @@ impl Query {
283 } 304 }
284} 305}
285 306
307fn contains_query(query: &Query, input_path: &ImportPath, enforce_lowercase: bool) -> bool {
308 let mut input = if query.name_only {
309 input_path.segments.last().unwrap().to_string()
310 } else {
311 input_path.to_string()
312 };
313 if enforce_lowercase || !query.case_sensitive {
314 input.make_ascii_lowercase();
315 }
316
317 let query_string =
318 if !enforce_lowercase && query.case_sensitive { &query.query } else { &query.lowercased };
319
320 match query.search_mode {
321 SearchMode::Equals => &input == query_string,
322 SearchMode::Contains => input.contains(query_string),
323 SearchMode::Fuzzy => {
324 let mut unchecked_query_chars = query_string.chars();
325 let mut mismatching_query_char = unchecked_query_chars.next();
326
327 for input_char in input.chars() {
328 match mismatching_query_char {
329 None => return true,
330 Some(matching_query_char) if matching_query_char == input_char => {
331 mismatching_query_char = unchecked_query_chars.next();
332 }
333 _ => (),
334 }
335 }
336 mismatching_query_char.is_none()
337 }
338 }
339}
340
286/// Searches dependencies of `krate` for an importable path matching `query`. 341/// Searches dependencies of `krate` for an importable path matching `query`.
287/// 342///
288/// This returns a list of items that could be imported from dependencies of `krate`. 343/// This returns a list of items that could be imported from dependencies of `krate`.
@@ -312,39 +367,29 @@ pub fn search_dependencies<'a>(
312 let importables = &import_map.importables[indexed_value.value as usize..]; 367 let importables = &import_map.importables[indexed_value.value as usize..];
313 368
314 // Path shared by the importable items in this group. 369 // Path shared by the importable items in this group.
315 let path = &import_map.map[&importables[0]].path; 370 let common_importables_path = &import_map.map[&importables[0]].path;
316 371 if !contains_query(&query, common_importables_path, true) {
317 if query.anchor_end { 372 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 } 373 }
324 374
375 let common_importables_path_fst = fst_path(common_importables_path);
325 // Add the items from this `ModPath` group. Those are all subsequent items in 376 // Add the items from this `ModPath` group. Those are all subsequent items in
326 // `importables` whose paths match `path`. 377 // `importables` whose paths match `path`.
327 let iter = importables 378 let iter = importables
328 .iter() 379 .iter()
329 .copied() 380 .copied()
330 .take_while(|item| { 381 .take_while(|item| {
331 let item_path = &import_map.map[item].path; 382 common_importables_path_fst == fst_path(&import_map.map[item].path)
332 fst_path(item_path) == fst_path(path)
333 }) 383 })
334 .filter(|&item| match item_import_kind(item) { 384 .filter(|&item| match item_import_kind(item) {
335 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind), 385 Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
336 None => true, 386 None => true,
387 })
388 .filter(|item| {
389 !query.case_sensitive // we've already checked the common importables path case-insensitively
390 || contains_query(&query, &import_map.map[item].path, false)
337 }); 391 });
338 392 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 393
349 if res.len() >= query.limit { 394 if res.len() >= query.limit {
350 res.truncate(query.limit); 395 res.truncate(query.limit);
@@ -388,7 +433,7 @@ mod tests {
388 use base_db::{fixture::WithFixture, SourceDatabase, Upcast}; 433 use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
389 use expect_test::{expect, Expect}; 434 use expect_test::{expect, Expect};
390 435
391 use crate::{test_db::TestDB, AssocContainerId, Lookup}; 436 use crate::{data::FunctionData, test_db::TestDB, AssocContainerId, Lookup};
392 437
393 use super::*; 438 use super::*;
394 439
@@ -407,14 +452,31 @@ mod tests {
407 .into_iter() 452 .into_iter()
408 .filter_map(|item| { 453 .filter_map(|item| {
409 let mark = match item { 454 let mark = match item {
455 ItemInNs::Types(ModuleDefId::FunctionId(_))
456 | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
410 ItemInNs::Types(_) => "t", 457 ItemInNs::Types(_) => "t",
411 ItemInNs::Values(_) => "v", 458 ItemInNs::Values(_) => "v",
412 ItemInNs::Macros(_) => "m", 459 ItemInNs::Macros(_) => "m",
413 }; 460 };
414 let item = assoc_to_trait(&db, item);
415 item.krate(db.upcast()).map(|krate| { 461 item.krate(db.upcast()).map(|krate| {
416 let map = db.import_map(krate); 462 let map = db.import_map(krate);
417 let path = map.path_of(item).unwrap(); 463
464 let path = match assoc_to_trait(&db, item) {
465 Some(trait_) => {
466 let mut full_path = map.path_of(trait_).unwrap().to_string();
467 if let ItemInNs::Types(ModuleDefId::FunctionId(function_id))
468 | ItemInNs::Values(ModuleDefId::FunctionId(function_id)) = item
469 {
470 full_path += &format!(
471 "::{}",
472 FunctionData::fn_data_query(&db, function_id).name
473 );
474 }
475 full_path
476 }
477 None => map.path_of(item).unwrap().to_string(),
478 };
479
418 format!( 480 format!(
419 "{}::{} ({})\n", 481 "{}::{} ({})\n",
420 crate_graph[krate].display_name.as_ref().unwrap(), 482 crate_graph[krate].display_name.as_ref().unwrap(),
@@ -427,15 +489,15 @@ mod tests {
427 expect.assert_eq(&actual) 489 expect.assert_eq(&actual)
428 } 490 }
429 491
430 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs { 492 fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
431 let assoc: AssocItemId = match item { 493 let assoc: AssocItemId = match item {
432 ItemInNs::Types(it) | ItemInNs::Values(it) => match it { 494 ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
433 ModuleDefId::TypeAliasId(it) => it.into(), 495 ModuleDefId::TypeAliasId(it) => it.into(),
434 ModuleDefId::FunctionId(it) => it.into(), 496 ModuleDefId::FunctionId(it) => it.into(),
435 ModuleDefId::ConstId(it) => it.into(), 497 ModuleDefId::ConstId(it) => it.into(),
436 _ => return item, 498 _ => return None,
437 }, 499 },
438 _ => return item, 500 _ => return None,
439 }; 501 };
440 502
441 let container = match assoc { 503 let container = match assoc {
@@ -445,8 +507,8 @@ mod tests {
445 }; 507 };
446 508
447 match container { 509 match container {
448 AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()), 510 AssocContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
449 _ => item, 511 _ => None,
450 } 512 }
451 } 513 }
452 514
@@ -685,7 +747,7 @@ mod tests {
685 } 747 }
686 748
687 #[test] 749 #[test]
688 fn search() { 750 fn search_mode() {
689 let ra_fixture = r#" 751 let ra_fixture = r#"
690 //- /main.rs crate:main deps:dep 752 //- /main.rs crate:main deps:dep
691 //- /dep.rs crate:dep deps:tdep 753 //- /dep.rs crate:dep deps:tdep
@@ -713,28 +775,96 @@ mod tests {
713 check_search( 775 check_search(
714 ra_fixture, 776 ra_fixture,
715 "main", 777 "main",
716 Query::new("fmt"), 778 Query::new("fmt".to_string()).search_mode(SearchMode::Fuzzy),
717 expect![[r#" 779 expect![[r#"
718 dep::fmt (t) 780 dep::fmt (t)
719 dep::Fmt (t) 781 dep::Fmt (t)
720 dep::Fmt (v) 782 dep::Fmt (v)
721 dep::Fmt (m) 783 dep::Fmt (m)
722 dep::fmt::Display (t) 784 dep::fmt::Display (t)
723 dep::format (v) 785 dep::format (f)
786 dep::fmt::Display::fmt (f)
787 "#]],
788 );
789
790 check_search(
791 ra_fixture,
792 "main",
793 Query::new("fmt".to_string()).search_mode(SearchMode::Equals),
794 expect![[r#"
795 dep::fmt (t)
796 dep::Fmt (t)
797 dep::Fmt (v)
798 dep::Fmt (m)
799 dep::fmt::Display::fmt (f)
800 "#]],
801 );
802
803 check_search(
804 ra_fixture,
805 "main",
806 Query::new("fmt".to_string()).search_mode(SearchMode::Contains),
807 expect![[r#"
808 dep::fmt (t)
809 dep::Fmt (t)
810 dep::Fmt (v)
811 dep::Fmt (m)
724 dep::fmt::Display (t) 812 dep::fmt::Display (t)
813 dep::fmt::Display::fmt (f)
725 "#]], 814 "#]],
726 ); 815 );
816 }
817
818 #[test]
819 fn name_only() {
820 let ra_fixture = r#"
821 //- /main.rs crate:main deps:dep
822 //- /dep.rs crate:dep deps:tdep
823 use tdep::fmt as fmt_dep;
824 pub mod fmt {
825 pub trait Display {
826 fn fmt();
827 }
828 }
829 #[macro_export]
830 macro_rules! Fmt {
831 () => {};
832 }
833 pub struct Fmt;
834
835 pub fn format() {}
836 pub fn no() {}
837
838 //- /tdep.rs crate:tdep
839 pub mod fmt {
840 pub struct NotImportableFromMain;
841 }
842 "#;
727 843
728 check_search( 844 check_search(
729 ra_fixture, 845 ra_fixture,
730 "main", 846 "main",
731 Query::new("fmt").anchor_end(), 847 Query::new("fmt".to_string()),
732 expect![[r#" 848 expect![[r#"
733 dep::fmt (t) 849 dep::fmt (t)
734 dep::Fmt (t) 850 dep::Fmt (t)
735 dep::Fmt (v) 851 dep::Fmt (v)
736 dep::Fmt (m) 852 dep::Fmt (m)
737 dep::fmt::Display (t) 853 dep::fmt::Display (t)
854 dep::fmt::Display::fmt (f)
855 "#]],
856 );
857
858 check_search(
859 ra_fixture,
860 "main",
861 Query::new("fmt".to_string()).name_only(),
862 expect![[r#"
863 dep::fmt (t)
864 dep::Fmt (t)
865 dep::Fmt (v)
866 dep::Fmt (m)
867 dep::fmt::Display::fmt (f)
738 "#]], 868 "#]],
739 ); 869 );
740 } 870 }
@@ -752,7 +882,7 @@ mod tests {
752 check_search( 882 check_search(
753 ra_fixture, 883 ra_fixture,
754 "main", 884 "main",
755 Query::new("FMT"), 885 Query::new("FMT".to_string()),
756 expect![[r#" 886 expect![[r#"
757 dep::fmt (t) 887 dep::fmt (t)
758 dep::fmt (v) 888 dep::fmt (v)
@@ -764,7 +894,7 @@ mod tests {
764 check_search( 894 check_search(
765 ra_fixture, 895 ra_fixture,
766 "main", 896 "main",
767 Query::new("FMT").case_sensitive(), 897 Query::new("FMT".to_string()).case_sensitive(),
768 expect![[r#" 898 expect![[r#"
769 dep::FMT (t) 899 dep::FMT (t)
770 dep::FMT (v) 900 dep::FMT (v)
@@ -793,7 +923,7 @@ mod tests {
793 pub fn no() {} 923 pub fn no() {}
794 "#, 924 "#,
795 "main", 925 "main",
796 Query::new("").limit(2), 926 Query::new("".to_string()).limit(2),
797 expect![[r#" 927 expect![[r#"
798 dep::fmt (t) 928 dep::fmt (t)
799 dep::Fmt (t) 929 dep::Fmt (t)
@@ -814,7 +944,7 @@ mod tests {
814 check_search( 944 check_search(
815 ra_fixture, 945 ra_fixture,
816 "main", 946 "main",
817 Query::new("FMT"), 947 Query::new("FMT".to_string()),
818 expect![[r#" 948 expect![[r#"
819 dep::fmt (t) 949 dep::fmt (t)
820 dep::fmt (v) 950 dep::fmt (v)
@@ -826,7 +956,7 @@ mod tests {
826 check_search( 956 check_search(
827 ra_fixture, 957 ra_fixture,
828 "main", 958 "main",
829 Query::new("FMT").exclude_import_kind(ImportKind::Adt), 959 Query::new("FMT".to_string()).exclude_import_kind(ImportKind::Adt),
830 expect![[r#""#]], 960 expect![[r#""#]],
831 ); 961 );
832 } 962 }