Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/child_modules.rs')
-rw-r--r--crates/ide/src/child_modules.rs123
1 files changed, 123 insertions, 0 deletions
diff --git a/crates/ide/src/child_modules.rs b/crates/ide/src/child_modules.rs
new file mode 100644
index 0000000000..b781596187
--- /dev/null
+++ b/crates/ide/src/child_modules.rs
@@ -0,0 +1,123 @@
+use hir::Semantics;
+use ide_db::{FilePosition, RootDatabase};
+use syntax::{
+ algo::find_node_at_offset,
+ ast::{self, AstNode},
+};
+
+use crate::NavigationTarget;
+
+// Feature: Child Modules
+//
+// Navigates to the child modules of the current module.
+//
+// | Editor | Action Name |
+// |---------|-------------|
+// | VS Code | **rust-analyzer: Locate child modules** |
+
+/// This returns `Vec` because a module may be included from several places.
+pub(crate) fn child_modules(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
+ let sema = Semantics::new(db);
+ let source_file = sema.parse_guess_edition(position.file_id);
+ // First go to the parent module which contains the cursor
+ let module = find_node_at_offset::<ast::Module>(source_file.syntax(), position.offset);
+
+ match module {
+ Some(module) => {
+ // Return all child modules inside the ItemList of the parent module
+ sema.to_def(&module)
+ .into_iter()
+ .flat_map(|module| module.children(db))
+ .map(|module| NavigationTarget::from_module_to_decl(db, module).call_site())
+ .collect()
+ }
+ None => {
+ // Return all the child modules inside the source file
+ sema.file_to_module_defs(position.file_id)
+ .flat_map(|module| module.children(db))
+ .map(|module| NavigationTarget::from_module_to_decl(db, module).call_site())
+ .collect()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use ide_db::FileRange;
+
+ use crate::fixture;
+
+ fn check_child_module(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
+ let (analysis, position, expected) = fixture::annotations(ra_fixture);
+ let navs = analysis.child_modules(position).unwrap();
+ let navs = navs
+ .iter()
+ .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
+ .collect::<Vec<_>>();
+ assert_eq!(expected.into_iter().map(|(fr, _)| fr).collect::<Vec<_>>(), navs);
+ }
+
+ #[test]
+ fn test_resolve_child_module() {
+ check_child_module(
+ r#"
+//- /lib.rs
+$0
+mod foo;
+ //^^^
+
+//- /foo.rs
+// empty
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_child_module_on_module_decl() {
+ check_child_module(
+ r#"
+//- /lib.rs
+mod $0foo;
+//- /foo.rs
+mod bar;
+ //^^^
+
+//- /foo/bar.rs
+// empty
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_child_module_for_inline() {
+ check_child_module(
+ r#"
+//- /lib.rs
+mod foo {
+ mod $0bar {
+ mod baz {}
+ } //^^^
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_resolve_multi_child_module() {
+ check_child_module(
+ r#"
+//- /main.rs
+$0
+mod foo;
+ //^^^
+mod bar;
+ //^^^
+//- /foo.rs
+// empty
+
+//- /bar.rs
+// empty
+"#,
+ );
+ }
+}