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