1use std::{
24 cmp::Ordering,
25 fmt,
26 hash::{Hash, Hasher},
27 ops::ControlFlow,
28};
29
30use base_db::{
31 CrateOrigin, InternedSourceRootId, LangCrateOrigin, LibraryRoots, LocalRoots, SourceRootId,
32 salsa::SalsaValue, source_root_crates,
33};
34use fst::{Automaton, Streamer, raw::IndexedValue};
35use hir::{
36 Crate, Module,
37 db::HirDatabase,
38 import_map::{AssocSearchMode, SearchMode},
39 symbols::{FileSymbol, SymbolCollector},
40};
41use itertools::Itertools;
42use rayon::prelude::*;
43
44use crate::RootDatabase;
45
46#[derive(Debug, Clone)]
60pub struct Query {
61 query: String,
64 lowercased: String,
67 path_filter: Vec<String>,
70 anchor_to_crate: bool,
72 mode: SearchMode,
79 assoc_mode: AssocSearchMode,
86 case_sensitive: bool,
91 only_types: bool,
97 libs: bool,
102 exclude_imports: bool,
107}
108
109impl Query {
110 pub fn new(query: String) -> Query {
111 let (path_filter, item_query, anchor_to_crate) = Self::parse_path_query(&query);
112 let lowercased = item_query.to_lowercase();
113 Query {
114 query: item_query,
115 lowercased,
116 path_filter,
117 anchor_to_crate,
118 only_types: false,
119 libs: false,
120 mode: SearchMode::Fuzzy,
121 assoc_mode: AssocSearchMode::Include,
122 case_sensitive: false,
123 exclude_imports: false,
124 }
125 }
126
127 fn parse_path_query(query: &str) -> (Vec<String>, String, bool) {
134 let (query, anchor_to_crate) = match query.strip_prefix("::") {
136 Some(q) => (q, true),
137 None => (query, false),
138 };
139
140 let Some((prefix, query)) = query.rsplit_once("::") else {
141 return (vec![], query.to_owned(), anchor_to_crate);
142 };
143
144 let prefix: Vec<_> =
145 prefix.split("::").filter(|s| !s.is_empty()).map(ToOwned::to_owned).collect();
146
147 (prefix, query.to_owned(), anchor_to_crate)
148 }
149
150 fn is_crate_search(&self) -> bool {
153 self.anchor_to_crate && self.path_filter.is_empty()
154 }
155
156 pub fn only_types(&mut self) {
157 self.only_types = true;
158 }
159
160 pub fn libs(&mut self) {
161 self.libs = true;
162 }
163
164 pub fn fuzzy(&mut self) {
165 self.mode = SearchMode::Fuzzy;
166 }
167
168 pub fn exact(&mut self) {
169 self.mode = SearchMode::Exact;
170 }
171
172 pub fn prefix(&mut self) {
173 self.mode = SearchMode::Prefix;
174 }
175
176 pub fn assoc_search_mode(&mut self, assoc_mode: AssocSearchMode) {
178 self.assoc_mode = assoc_mode;
179 }
180
181 pub fn case_sensitive(&mut self) {
182 self.case_sensitive = true;
183 }
184
185 pub fn exclude_imports(&mut self) {
186 self.exclude_imports = true;
187 }
188}
189
190pub fn crate_symbols(db: &dyn HirDatabase, krate: Crate) -> Box<[&SymbolIndex<'_>]> {
192 let _p = tracing::info_span!("crate_symbols").entered();
193 krate.modules(db).into_iter().map(|module| SymbolIndex::module_symbols(db, module)).collect()
194}
195
196pub fn world_symbols(db: &RootDatabase, mut query: Query) -> Vec<FileSymbol<'_>> {
225 let _p = tracing::info_span!("world_symbols", query = ?query.query).entered();
226
227 let indices: Vec<_> = if query.is_crate_search() {
229 query.only_types = false;
230 vec![SymbolIndex::extern_prelude_symbols(db)]
231 } else if !query.path_filter.is_empty() {
233 query.only_types = false;
234 let target_modules = resolve_path_to_modules(
235 db,
236 &query.path_filter,
237 query.anchor_to_crate,
238 query.case_sensitive,
239 );
240
241 if target_modules.is_empty() {
242 return vec![];
243 }
244
245 target_modules.iter().map(|&module| SymbolIndex::module_symbols(db, module)).collect()
246 } else if query.libs {
247 LibraryRoots::get(db)
248 .roots(db)
249 .par_iter()
250 .for_each_with(db.clone(), |snap, &root| _ = SymbolIndex::library_symbols(snap, root));
251 LibraryRoots::get(db)
252 .roots(db)
253 .iter()
254 .map(|&root| SymbolIndex::library_symbols(db, root))
255 .collect()
256 } else {
257 let mut crates = Vec::new();
258
259 for &root in LocalRoots::get(db).roots(db).iter() {
260 crates.extend(source_root_crates(db, root).iter().copied())
261 }
262 crates
263 .par_iter()
264 .for_each_with(db.clone(), |snap, &krate| _ = crate_symbols(snap, krate.into()));
265 crates
266 .into_iter()
267 .flat_map(|krate| Vec::from(crate_symbols(db, krate.into())))
268 .chain(std::iter::once(SymbolIndex::extern_prelude_symbols(db)))
269 .collect()
270 };
271
272 let mut res = vec![];
273
274 query.search::<()>(db, &indices, |f| {
276 res.push(f.clone());
277 ControlFlow::Continue(())
278 });
279
280 res
281}
282
283fn resolve_path_to_modules(
291 db: &dyn HirDatabase,
292 path_filter: &[String],
293 anchor_to_crate: bool,
294 case_sensitive: bool,
295) -> Vec<Module> {
296 let [first_segment, rest_segments @ ..] = path_filter else {
297 return vec![];
298 };
299
300 let names_match = |actual: &str, expected: &str| -> bool {
302 if case_sensitive { actual == expected } else { actual.eq_ignore_ascii_case(expected) }
303 };
304
305 let matching_crates: Vec<Crate> = Crate::all(db)
307 .into_iter()
308 .filter(|krate| {
309 krate
310 .display_name(db)
311 .is_some_and(|name| names_match(name.crate_name().as_str(), first_segment))
312 })
313 .collect();
314
315 let mut candidate_modules: Vec<(Module, bool)> = vec![];
318
319 for krate in matching_crates {
321 candidate_modules.push((krate.root_module(db), krate.origin(db).is_local()));
322 }
323
324 if !anchor_to_crate {
326 for &root in LocalRoots::get(db).roots(db).iter() {
327 for &krate in source_root_crates(db, root).iter() {
328 let root_module = Crate::from(krate).root_module(db);
329 for child in root_module.children(db) {
330 if let Some(name) = child.name(db)
331 && names_match(name.as_str(), first_segment)
332 {
333 candidate_modules.push((child, true));
334 }
335 }
336 }
337 }
338 }
339
340 for segment in rest_segments {
342 candidate_modules = candidate_modules
343 .into_iter()
344 .flat_map(|(module, local)| {
345 module
346 .modules_in_scope(db, !local)
347 .into_iter()
348 .filter(|(name, _)| names_match(name.as_str(), segment))
349 .map(move |(_, module)| (module, local))
350 })
351 .unique()
352 .collect();
353
354 if candidate_modules.is_empty() {
355 break;
356 }
357 }
358
359 candidate_modules.into_iter().map(|(module, _)| module).collect()
360}
361
362#[derive(Default, SalsaValue)]
363pub struct SymbolIndex<'db> {
364 symbols: Box<[FileSymbol<'db>]>,
365 map: fst::Map<Vec<u8>>,
366}
367
368impl<'db> SymbolIndex<'db> {
369 pub fn library_symbols(
371 db: &'db dyn HirDatabase,
372 source_root_id: SourceRootId,
373 ) -> &'db SymbolIndex<'db> {
374 #[salsa::tracked(returns(ref))]
375 fn library_symbols<'db>(
376 db: &'db dyn HirDatabase,
377 source_root_id: InternedSourceRootId<'db>,
378 ) -> SymbolIndex<'db> {
379 let _p = tracing::info_span!("library_symbols").entered();
380
381 hir::attach_db(db, || {
383 let mut symbol_collector = SymbolCollector::new(db, true);
384
385 source_root_crates(db, source_root_id.id(db))
386 .iter()
387 .flat_map(|&krate| Crate::from(krate).modules(db))
388 .for_each(|module| symbol_collector.collect(module));
392
393 SymbolIndex::new(symbol_collector.finish())
394 })
395 }
396 library_symbols(db, InternedSourceRootId::new(db, source_root_id))
397 }
398
399 pub fn module_symbols(db: &dyn HirDatabase, module: Module) -> &SymbolIndex<'_> {
402 #[salsa::tracked(returns(ref))]
403 fn module_symbols<'db>(
404 db: &'db dyn HirDatabase,
405 module: hir::ModuleId,
406 ) -> SymbolIndex<'db> {
407 let _p = tracing::info_span!("module_symbols").entered();
408
409 hir::attach_db(db, || {
411 let module: Module = module.into();
412 SymbolIndex::new(SymbolCollector::new_module(
413 db,
414 module,
415 !module.krate(db).origin(db).is_local(),
416 ))
417 })
418 }
419
420 module_symbols(db, hir::ModuleId::from(module))
421 }
422
423 pub fn extern_prelude_symbols(db: &dyn HirDatabase) -> &SymbolIndex<'_> {
425 #[salsa::tracked(returns(ref))]
426 fn extern_prelude_symbols<'db>(db: &'db dyn HirDatabase) -> SymbolIndex<'db> {
427 let _p = tracing::info_span!("extern_prelude_symbols").entered();
428
429 hir::attach_db(db, || {
431 let mut collector = SymbolCollector::new(db, false);
432
433 for krate in Crate::all(db) {
434 if krate
435 .display_name(db)
436 .is_none_or(|name| name.canonical_name().as_str() == "build-script-build")
437 {
438 continue;
439 }
440 if let CrateOrigin::Lang(LangCrateOrigin::Dependency | LangCrateOrigin::Other) =
441 krate.origin(db)
442 {
443 continue;
445 }
446 collector.push_crate_root(krate);
447 }
448
449 SymbolIndex::new(collector.finish())
450 })
451 }
452
453 extern_prelude_symbols(db)
454 }
455}
456
457impl fmt::Debug for SymbolIndex<'_> {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
460 }
461}
462
463impl PartialEq for SymbolIndex<'_> {
464 fn eq(&self, other: &SymbolIndex<'_>) -> bool {
465 self.symbols == other.symbols
466 }
467}
468
469impl Eq for SymbolIndex<'_> {}
470
471impl Hash for SymbolIndex<'_> {
472 fn hash<H: Hasher>(&self, hasher: &mut H) {
473 self.symbols.hash(hasher)
474 }
475}
476
477impl<'db> SymbolIndex<'db> {
478 fn new(mut symbols: Box<[FileSymbol<'db>]>) -> SymbolIndex<'db> {
479 fn cmp(lhs: &FileSymbol<'_>, rhs: &FileSymbol<'_>) -> Ordering {
480 let lhs_chars = lhs.name.as_str().chars().map(|c| c.to_ascii_lowercase());
481 let rhs_chars = rhs.name.as_str().chars().map(|c| c.to_ascii_lowercase());
482 lhs_chars.cmp(rhs_chars)
483 }
484
485 symbols.par_sort_by(cmp);
486
487 let mut builder = fst::MapBuilder::memory();
488
489 let mut last_batch_start = 0;
490
491 for idx in 0..symbols.len() {
492 if let Some(next_symbol) = symbols.get(idx + 1)
493 && cmp(&symbols[last_batch_start], next_symbol) == Ordering::Equal
494 {
495 continue;
496 }
497
498 let start = last_batch_start;
499 let end = idx + 1;
500 last_batch_start = end;
501
502 let key = symbols[start].name.as_str().to_ascii_lowercase();
503 let value = SymbolIndex::range_to_map_value(start, end);
504
505 builder.insert(key, value).unwrap();
506 }
507
508 let map = builder
509 .into_inner()
510 .and_then(|mut buf| {
511 fst::Map::new({
512 buf.shrink_to_fit();
513 buf
514 })
515 })
516 .unwrap();
517 SymbolIndex { symbols, map }
518 }
519
520 pub fn len(&self) -> usize {
521 self.symbols.len()
522 }
523
524 pub fn memory_size(&self) -> usize {
525 self.map.as_fst().size() + self.symbols.len() * size_of::<FileSymbol<'_>>()
526 }
527
528 fn range_to_map_value(start: usize, end: usize) -> u64 {
529 debug_assert![start <= (u32::MAX as usize)];
530 debug_assert![end <= (u32::MAX as usize)];
531
532 ((start as u64) << 32) | end as u64
533 }
534
535 fn map_value_to_range(value: u64) -> (usize, usize) {
536 let end = value as u32 as usize;
537 let start = (value >> 32) as usize;
538 (start, end)
539 }
540}
541
542impl Query {
543 pub(crate) fn search<'db, T>(
545 &self,
546 db: &'db RootDatabase,
547 indices: &[&'db SymbolIndex<'db>],
548 cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow<T>,
549 ) -> Option<T> {
550 let _p = tracing::info_span!("symbol_index::Query::search").entered();
551
552 let mut op = fst::map::OpBuilder::new();
553 match self.mode {
554 SearchMode::Exact => {
555 let automaton = fst::automaton::Str::new(&self.lowercased);
556
557 for index in indices.iter() {
558 op = op.add(index.map.search(&automaton));
559 }
560 self.search_maps(db, indices, op.union(), cb)
561 }
562 SearchMode::Fuzzy => {
563 let automaton = fst::automaton::Subsequence::new(&self.lowercased);
564
565 for index in indices.iter() {
566 op = op.add(index.map.search(&automaton));
567 }
568 self.search_maps(db, indices, op.union(), cb)
569 }
570 SearchMode::Prefix => {
571 let automaton = fst::automaton::Str::new(&self.lowercased).starts_with();
572
573 for index in indices.iter() {
574 op = op.add(index.map.search(&automaton));
575 }
576 self.search_maps(db, indices, op.union(), cb)
577 }
578 }
579 }
580
581 fn search_maps<'db, T>(
582 &self,
583 db: &'db RootDatabase,
584 indices: &[&'db SymbolIndex<'db>],
585 mut stream: fst::map::Union<'_>,
586 mut cb: impl FnMut(&'db FileSymbol<'db>) -> ControlFlow<T>,
587 ) -> Option<T> {
588 let ignore_underscore_prefixed = !self.query.starts_with("__");
589 while let Some((_, indexed_values)) = stream.next() {
590 for &IndexedValue { index, value } in indexed_values {
591 let symbol_index = indices[index];
592 let (start, end) = SymbolIndex::map_value_to_range(value);
593
594 for symbol in &symbol_index.symbols[start..end] {
595 let non_type_for_type_only_query = self.only_types
596 && !(matches!(
597 symbol.def,
598 hir::ModuleDef::Adt(..)
599 | hir::ModuleDef::TypeAlias(..)
600 | hir::ModuleDef::BuiltinType(..)
601 | hir::ModuleDef::Trait(..)
602 ) || matches!(
603 symbol.def,
604 hir::ModuleDef::Module(module) if module.is_crate_root(db)
605 ));
606 if non_type_for_type_only_query || !self.matches_assoc_mode(symbol.is_assoc) {
607 continue;
608 }
609 let symbol_name = symbol.name.as_str();
611 if ignore_underscore_prefixed && symbol_name.starts_with("__") {
612 continue;
613 }
614 if self.exclude_imports && symbol.is_import {
615 continue;
616 }
617 if self.mode.check(&self.query, self.case_sensitive, symbol_name)
618 && let Some(b) = cb(symbol).break_value()
619 {
620 return Some(b);
621 }
622 }
623 }
624 }
625 None
626 }
627
628 fn matches_assoc_mode(&self, is_trait_assoc_item: bool) -> bool {
629 !matches!(
630 (is_trait_assoc_item, self.assoc_mode),
631 (true, AssocSearchMode::Exclude) | (false, AssocSearchMode::AssocItemsOnly)
632 )
633 }
634}
635
636#[cfg(test)]
637mod tests {
638
639 use expect_test::expect_file;
640 use rustc_hash::FxHashSet;
641 use salsa::Setter;
642 use test_fixture::{WORKSPACE, WithFixture};
643
644 use super::*;
645
646 #[test]
647 fn test_symbol_index_collection() {
648 let (db, _) = RootDatabase::with_many_files(
649 r#"
650//- /main.rs
651
652macro_rules! macro_rules_macro {
653 () => {}
654};
655
656macro_rules! define_struct {
657 () => {
658 struct StructFromMacro;
659 }
660};
661
662define_struct!();
663
664macro Macro { }
665
666struct Struct;
667enum Enum {
668 A, B
669}
670union Union {}
671
672impl Struct {
673 fn impl_fn() {}
674}
675
676struct StructT<T>;
677
678impl <T> StructT<T> {
679 fn generic_impl_fn() {}
680}
681
682trait Trait {
683 fn trait_fn(&self);
684}
685
686fn main() {
687 struct StructInFn;
688}
689
690const CONST: u32 = 1;
691static STATIC: &'static str = "2";
692type Alias = Struct;
693
694mod a_mod {
695 struct StructInModA;
696}
697
698const _: () = {
699 struct StructInUnnamedConst;
700
701 ()
702};
703
704const CONST_WITH_INNER: () = {
705 struct StructInNamedConst;
706
707 ()
708};
709
710mod b_mod;
711
712
713use define_struct as really_define_struct;
714use Macro as ItemLikeMacro;
715use Macro as Trait; // overlay namespaces
716//- /b_mod.rs
717struct StructInModB;
718pub(self) use super::Macro as SuperItemLikeMacro;
719pub(self) use crate::b_mod::StructInModB as ThisStruct;
720pub(self) use crate::Trait as IsThisJustATrait;
721"#,
722 );
723
724 let symbols: Vec<_> = Crate::from(db.test_crate())
725 .modules(&db)
726 .into_iter()
727 .map(|module_id| {
728 let mut symbols = SymbolCollector::new_module(&db, module_id, false);
729 symbols.sort_by_key(|it| it.name.as_str().to_owned());
730 (module_id, symbols)
731 })
732 .collect();
733
734 expect_file!["./test_data/test_symbol_index_collection.txt"].assert_debug_eq(&symbols);
735 }
736
737 #[test]
738 fn test_doc_alias() {
739 let (db, _) = RootDatabase::with_single_file(
740 r#"
741#[doc(alias="s1")]
742#[doc(alias="s2")]
743#[doc(alias("mul1","mul2"))]
744struct Struct;
745
746#[doc(alias="s1")]
747struct Duplicate;
748 "#,
749 );
750
751 let symbols: Vec<_> = Crate::from(db.test_crate())
752 .modules(&db)
753 .into_iter()
754 .map(|module_id| {
755 let mut symbols = SymbolCollector::new_module(&db, module_id, false);
756 symbols.sort_by_key(|it| it.name.as_str().to_owned());
757 (module_id, symbols)
758 })
759 .collect();
760
761 expect_file!["./test_data/test_doc_alias.txt"].assert_debug_eq(&symbols);
762 }
763
764 #[test]
765 fn test_exclude_imports() {
766 let (mut db, _) = RootDatabase::with_many_files(
767 r#"
768//- /lib.rs
769mod foo;
770pub use foo::Foo;
771
772//- /foo.rs
773pub struct Foo;
774"#,
775 );
776
777 let mut local_roots = FxHashSet::default();
778 local_roots.insert(WORKSPACE);
779 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
780
781 let mut query = Query::new("Foo".to_owned());
782 let mut symbols = world_symbols(&db, query.clone());
783 symbols.sort_by_key(|x| x.is_import);
784 expect_file!["./test_data/test_symbols_with_imports.txt"].assert_debug_eq(&symbols);
785
786 query.exclude_imports();
787 let symbols = world_symbols(&db, query);
788 expect_file!["./test_data/test_symbols_exclude_imports.txt"].assert_debug_eq(&symbols);
789 }
790
791 #[test]
792 fn test_parse_path_query() {
793 let (path, item, anchor) = Query::parse_path_query("Item");
795 assert_eq!(path, Vec::<String>::new());
796 assert_eq!(item, "Item");
797 assert!(!anchor);
798
799 let (path, item, anchor) = Query::parse_path_query("foo::Item");
801 assert_eq!(path, vec!["foo"]);
802 assert_eq!(item, "Item");
803 assert!(!anchor);
804
805 let (path, item, anchor) = Query::parse_path_query("foo::bar::Item");
807 assert_eq!(path, vec!["foo", "bar"]);
808 assert_eq!(item, "Item");
809 assert!(!anchor);
810
811 let (path, item, anchor) = Query::parse_path_query("::std::vec::Vec");
813 assert_eq!(path, vec!["std", "vec"]);
814 assert_eq!(item, "Vec");
815 assert!(anchor);
816
817 let (path, item, anchor) = Query::parse_path_query("::");
819 assert_eq!(path, Vec::<String>::new());
820 assert_eq!(item, "");
821 assert!(anchor);
822
823 let (path, item, anchor) = Query::parse_path_query("::foo");
825 assert_eq!(path, Vec::<String>::new());
826 assert_eq!(item, "foo");
827 assert!(anchor);
828
829 let (path, item, anchor) = Query::parse_path_query("foo::");
831 assert_eq!(path, vec!["foo"]);
832 assert_eq!(item, "");
833 assert!(!anchor);
834
835 let (path, item, anchor) = Query::parse_path_query("foo::bar::");
837 assert_eq!(path, vec!["foo", "bar"]);
838 assert_eq!(item, "");
839 assert!(!anchor);
840
841 let (path, item, anchor) = Query::parse_path_query("::std::vec::");
843 assert_eq!(path, vec!["std", "vec"]);
844 assert_eq!(item, "");
845 assert!(anchor);
846
847 let (path, item, anchor) = Query::parse_path_query("foo::::bar");
849 assert_eq!(path, vec!["foo"]);
850 assert_eq!(item, "bar");
851 assert!(!anchor);
852 }
853
854 #[test]
855 fn test_path_search() {
856 let (mut db, _) = RootDatabase::with_many_files(
857 r#"
858//- /lib.rs crate:main
859mod inner;
860pub struct RootStruct;
861
862//- /inner.rs
863pub struct InnerStruct;
864pub mod nested {
865 pub struct NestedStruct;
866}
867"#,
868 );
869
870 let mut local_roots = FxHashSet::default();
871 local_roots.insert(WORKSPACE);
872 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
873
874 let query = Query::new("inner::InnerStruct".to_owned());
876 let symbols = world_symbols(&db, query);
877 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
878 assert!(names.contains(&"InnerStruct"), "Expected InnerStruct in {:?}", names);
879
880 let query = Query::new("inner::nested::NestedStruct".to_owned());
882 let symbols = world_symbols(&db, query);
883 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
884 assert!(names.contains(&"NestedStruct"), "Expected NestedStruct in {:?}", names);
885
886 let query = Query::new("main::inner::InnerStruct".to_owned());
888 let symbols = world_symbols(&db, query);
889 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
890 assert!(names.contains(&"InnerStruct"), "Expected InnerStruct in {:?}", names);
891
892 let query = Query::new("wrong::InnerStruct".to_owned());
894 let symbols = world_symbols(&db, query);
895 assert!(symbols.is_empty(), "Expected empty results for wrong path");
896 }
897
898 #[test]
899 fn test_path_search_module() {
900 let (mut db, _) = RootDatabase::with_many_files(
901 r#"
902//- /lib.rs crate:main
903mod mymod;
904
905//- /mymod.rs
906pub struct MyStruct;
907pub fn my_func() {}
908pub const MY_CONST: u32 = 1;
909"#,
910 );
911
912 let mut local_roots = FxHashSet::default();
913 local_roots.insert(WORKSPACE);
914 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
915
916 let query = Query::new("main::mymod::".to_owned());
918 let symbols = world_symbols(&db, query);
919 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
920
921 assert!(names.contains(&"MyStruct"), "Expected MyStruct in {:?}", names);
922 assert!(names.contains(&"my_func"), "Expected my_func in {:?}", names);
923 assert!(names.contains(&"MY_CONST"), "Expected MY_CONST in {:?}", names);
924 }
925
926 #[test]
927 fn test_fuzzy_item_with_path() {
928 let (mut db, _) = RootDatabase::with_many_files(
929 r#"
930//- /lib.rs crate:main
931mod mymod;
932
933//- /mymod.rs
934pub struct MyLongStructName;
935"#,
936 );
937
938 let mut local_roots = FxHashSet::default();
939 local_roots.insert(WORKSPACE);
940 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
941
942 let query = Query::new("main::mymod::MyLong".to_owned());
944 let symbols = world_symbols(&db, query);
945 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
946 assert!(
947 names.contains(&"MyLongStructName"),
948 "Expected fuzzy match for MyLongStructName in {:?}",
949 names
950 );
951 }
952
953 #[test]
954 fn test_case_insensitive_path() {
955 let (mut db, _) = RootDatabase::with_many_files(
956 r#"
957//- /lib.rs crate:main
958mod MyMod;
959
960//- /MyMod.rs
961pub struct MyStruct;
962"#,
963 );
964
965 let mut local_roots = FxHashSet::default();
966 local_roots.insert(WORKSPACE);
967 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
968
969 let query = Query::new("main::mymod::MyStruct".to_owned());
971 let symbols = world_symbols(&db, query);
972 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
973 assert!(names.contains(&"MyStruct"), "Expected case-insensitive match in {:?}", names);
974 }
975
976 #[test]
977 fn test_absolute_path_search() {
978 let (mut db, _) = RootDatabase::with_many_files(
979 r#"
980//- /lib.rs crate:mycrate
981mod inner;
982pub struct CrateRoot;
983
984//- /inner.rs
985pub struct InnerItem;
986"#,
987 );
988
989 let mut local_roots = FxHashSet::default();
990 local_roots.insert(WORKSPACE);
991 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
992
993 let query = Query::new("::mycrate::inner::InnerItem".to_owned());
995 let symbols = world_symbols(&db, query);
996 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
997 assert!(
998 names.contains(&"InnerItem"),
999 "Expected InnerItem with absolute path in {:?}",
1000 names
1001 );
1002
1003 let query = Query::new("::wrongcrate::inner::InnerItem".to_owned());
1005 let symbols = world_symbols(&db, query);
1006 assert!(symbols.is_empty(), "Expected empty results for wrong crate name");
1007 }
1008
1009 #[test]
1010 fn test_wrong_path_returns_empty() {
1011 let (mut db, _) = RootDatabase::with_many_files(
1012 r#"
1013//- /lib.rs crate:main
1014mod existing;
1015
1016//- /existing.rs
1017pub struct MyStruct;
1018"#,
1019 );
1020
1021 let mut local_roots = FxHashSet::default();
1022 local_roots.insert(WORKSPACE);
1023 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
1024
1025 let query = Query::new("nonexistent::MyStruct".to_owned());
1027 let symbols = world_symbols(&db, query);
1028 assert!(symbols.is_empty(), "Expected empty results for non-existent path");
1029
1030 let query = Query::new("wrongmod::MyStruct".to_owned());
1032 let symbols = world_symbols(&db, query);
1033 assert!(symbols.is_empty(), "Expected empty results for wrong module");
1034 }
1035
1036 #[test]
1037 fn test_root_module_items() {
1038 let (mut db, _) = RootDatabase::with_many_files(
1039 r#"
1040//- /lib.rs crate:mylib
1041pub struct RootItem;
1042pub fn root_fn() {}
1043"#,
1044 );
1045
1046 let mut local_roots = FxHashSet::default();
1047 local_roots.insert(WORKSPACE);
1048 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
1049
1050 let query = Query::new("mylib::RootItem".to_owned());
1052 let symbols = world_symbols(&db, query);
1053 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1054 assert!(names.contains(&"RootItem"), "Expected RootItem at crate root in {:?}", names);
1055
1056 let query = Query::new("mylib::".to_owned());
1057 let symbols = world_symbols(&db, query);
1058 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1059 assert!(names.contains(&"RootItem"), "Expected RootItem {:?}", names);
1060 assert!(names.contains(&"root_fn"), "Expected root_fn {:?}", names);
1061 }
1062
1063 #[test]
1064 fn test_crate_search_all() {
1065 let (mut db, _) = RootDatabase::with_many_files(
1067 r#"
1068//- /lib.rs crate:alpha
1069pub struct AlphaStruct;
1070
1071//- /beta.rs crate:beta
1072pub struct BetaStruct;
1073
1074//- /gamma.rs crate:gamma
1075pub struct GammaStruct;
1076"#,
1077 );
1078
1079 let mut local_roots = FxHashSet::default();
1080 local_roots.insert(WORKSPACE);
1081 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
1082
1083 let query = Query::new("::".to_owned());
1085 let symbols = world_symbols(&db, query);
1086 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1087
1088 assert!(names.contains(&"alpha"), "Expected alpha crate in {:?}", names);
1089 assert!(names.contains(&"beta"), "Expected beta crate in {:?}", names);
1090 assert!(names.contains(&"gamma"), "Expected gamma crate in {:?}", names);
1091 assert_eq!(symbols.len(), 3, "Expected exactly 3 crates, got {:?}", names);
1092 }
1093
1094 #[test]
1095 fn test_crate_search_fuzzy() {
1096 let (mut db, _) = RootDatabase::with_many_files(
1098 r#"
1099//- /lib.rs crate:my_awesome_lib
1100pub struct AwesomeStruct;
1101
1102//- /other.rs crate:another_lib
1103pub struct OtherStruct;
1104
1105//- /foo.rs crate:foobar
1106pub struct FooStruct;
1107"#,
1108 );
1109
1110 let mut local_roots = FxHashSet::default();
1111 local_roots.insert(WORKSPACE);
1112 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
1113
1114 let query = Query::new("::foo".to_owned());
1116 let symbols = world_symbols(&db, query);
1117 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1118
1119 assert!(names.contains(&"foobar"), "Expected foobar crate in {:?}", names);
1120 assert_eq!(symbols.len(), 1, "Expected only foobar crate, got {:?}", names);
1121
1122 let query = Query::new("::awesome".to_owned());
1124 let symbols = world_symbols(&db, query);
1125 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1126
1127 assert!(names.contains(&"my_awesome_lib"), "Expected my_awesome_lib crate in {:?}", names);
1128 assert_eq!(symbols.len(), 1, "Expected only my_awesome_lib crate, got {:?}", names);
1129
1130 let query = Query::new("::lib".to_owned());
1132 let symbols = world_symbols(&db, query);
1133 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1134
1135 assert!(names.contains(&"my_awesome_lib"), "Expected my_awesome_lib in {:?}", names);
1136 assert!(names.contains(&"another_lib"), "Expected another_lib in {:?}", names);
1137 assert_eq!(symbols.len(), 2, "Expected 2 crates matching 'lib', got {:?}", names);
1138
1139 let query = Query::new("::nonexistent".to_owned());
1141 let symbols = world_symbols(&db, query);
1142 assert!(symbols.is_empty(), "Expected empty results for non-matching crate pattern");
1143 }
1144
1145 #[test]
1146 fn test_path_search_with_use_reexport() {
1147 let (mut db, _) = RootDatabase::with_many_files(
1149 r#"
1150//- /lib.rs crate:main
1151mod inner;
1152pub use inner::nested;
1153
1154//- /inner.rs
1155pub mod nested {
1156 pub struct NestedStruct;
1157 pub fn nested_fn() {}
1158}
1159"#,
1160 );
1161
1162 let mut local_roots = FxHashSet::default();
1163 local_roots.insert(WORKSPACE);
1164 LocalRoots::get(&db).set_roots(&mut db).to(local_roots);
1165
1166 let query = Query::new("main::nested::NestedStruct".to_owned());
1169 let symbols = world_symbols(&db, query);
1170 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1171 assert!(
1172 names.contains(&"NestedStruct"),
1173 "Expected NestedStruct via re-exported path in {:?}",
1174 names
1175 );
1176
1177 let query = Query::new("main::inner::nested::NestedStruct".to_owned());
1179 let symbols = world_symbols(&db, query);
1180 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1181 assert!(
1182 names.contains(&"NestedStruct"),
1183 "Expected NestedStruct via original path in {:?}",
1184 names
1185 );
1186
1187 let query = Query::new("main::nested::".to_owned());
1189 let symbols = world_symbols(&db, query);
1190 let names: Vec<_> = symbols.iter().map(|s| s.name.as_str()).collect();
1191 assert!(
1192 names.contains(&"NestedStruct"),
1193 "Expected NestedStruct when browsing re-exported module in {:?}",
1194 names
1195 );
1196 assert!(
1197 names.contains(&"nested_fn"),
1198 "Expected nested_fn when browsing re-exported module in {:?}",
1199 names
1200 );
1201 }
1202}