Merge pull request #19255 from geetanshjuneja/master

Add children modules feature
diff --git a/crates/ide/src/children_modules.rs b/crates/ide/src/children_modules.rs
new file mode 100644
index 0000000..4bb7cb8
--- /dev/null
+++ b/crates/ide/src/children_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: Children Modules
+//
+// Navigates to the children modules of the current module.
+//
+// | Editor  | Action Name |
+// |---------|-------------|
+// | VS Code | **rust-analyzer: Locate children modules** |
+
+/// This returns `Vec` because a module may be included from several places.
+pub(crate) fn children_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 the children module 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 children module 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_children_module(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
+        let (analysis, position, expected) = fixture::annotations(ra_fixture);
+        let navs = analysis.children_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_children_module() {
+        check_children_module(
+            r#"
+//- /lib.rs
+$0
+mod foo;
+  //^^^
+
+//- /foo.rs
+// empty
+"#,
+        );
+    }
+
+    #[test]
+    fn test_resolve_children_module_on_module_decl() {
+        check_children_module(
+            r#"
+//- /lib.rs
+mod $0foo;
+//- /foo.rs
+mod bar;
+  //^^^
+
+//- /foo/bar.rs
+// empty
+"#,
+        );
+    }
+
+    #[test]
+    fn test_resolve_children_module_for_inline() {
+        check_children_module(
+            r#"
+//- /lib.rs
+mod foo {
+    mod $0bar {
+        mod baz {}
+    }     //^^^
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_resolve_multi_child_module() {
+        check_children_module(
+            r#"
+//- /main.rs
+$0
+mod foo;
+  //^^^
+mod bar;
+  //^^^
+//- /foo.rs
+// empty
+
+//- /bar.rs
+// empty
+"#,
+        );
+    }
+}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 13b161e..a517dd0 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -20,6 +20,7 @@
 
 mod annotations;
 mod call_hierarchy;
+mod children_modules;
 mod doc_links;
 mod expand_macro;
 mod extend_selection;
@@ -605,6 +606,11 @@
         self.with_db(|db| parent_module::parent_module(db, position))
     }
 
+    /// Returns vec of `mod name;` declaration which are created by the current module.
+    pub fn children_modules(&self, position: FilePosition) -> Cancellable<Vec<NavigationTarget>> {
+        self.with_db(|db| children_modules::children_modules(db, position))
+    }
+
     /// Returns crates that this file belongs to.
     pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<Crate>> {
         self.with_db(|db| parent_module::crates_for(db, file_id))
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index 75fe99e..0444a0e 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/crates/rust-analyzer/src/handlers/request.rs
@@ -943,6 +943,18 @@
     Ok(Some(res))
 }
 
+pub(crate) fn handle_children_modules(
+    snap: GlobalStateSnapshot,
+    params: lsp_types::TextDocumentPositionParams,
+) -> anyhow::Result<Option<lsp_types::GotoDefinitionResponse>> {
+    let _p = tracing::info_span!("handle_children_module").entered();
+    // locate children module by semantics
+    let position = try_default!(from_proto::file_position(&snap, params)?);
+    let navs = snap.analysis.children_modules(position)?;
+    let res = to_proto::goto_definition_response(&snap, None, navs)?;
+    Ok(Some(res))
+}
+
 pub(crate) fn handle_runnables(
     snap: GlobalStateSnapshot,
     params: lsp_ext::RunnablesParams,
diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs
index 7fa8e48..e751e7d 100644
--- a/crates/rust-analyzer/src/lsp/capabilities.rs
+++ b/crates/rust-analyzer/src/lsp/capabilities.rs
@@ -157,6 +157,7 @@
             "onEnter": true,
             "openCargoToml": true,
             "parentModule": true,
+            "childrenModules": true,
             "runnables": {
                 "kinds": [ "cargo" ],
             },
diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs
index 876a0db..ae4a9db 100644
--- a/crates/rust-analyzer/src/lsp/ext.rs
+++ b/crates/rust-analyzer/src/lsp/ext.rs
@@ -399,6 +399,14 @@
     const METHOD: &'static str = "experimental/parentModule";
 }
 
+pub enum ChildrenModules {}
+
+impl Request for ChildrenModules {
+    type Params = lsp_types::TextDocumentPositionParams;
+    type Result = Option<lsp_types::GotoDefinitionResponse>;
+    const METHOD: &'static str = "experimental/childrenModule";
+}
+
 pub enum JoinLines {}
 
 impl Request for JoinLines {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 47fcb5a..0b5d040 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -1172,6 +1172,7 @@
             .on::<NO_RETRY, lsp_ext::InterpretFunction>(handlers::handle_interpret_function)
             .on::<NO_RETRY, lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
             .on::<NO_RETRY, lsp_ext::ParentModule>(handlers::handle_parent_module)
+            .on::<NO_RETRY, lsp_ext::ChildrenModules>(handlers::handle_children_modules)
             .on::<NO_RETRY, lsp_ext::Runnables>(handlers::handle_runnables)
             .on::<NO_RETRY, lsp_ext::RelatedTests>(handlers::handle_related_tests)
             .on::<NO_RETRY, lsp_ext::CodeActionRequest>(handlers::handle_code_action)
diff --git a/docs/book/src/contributing/lsp-extensions.md b/docs/book/src/contributing/lsp-extensions.md
index 16217a7..8854f58 100644
--- a/docs/book/src/contributing/lsp-extensions.md
+++ b/docs/book/src/contributing/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp/ext.rs hash: 3549077514b37437
+lsp/ext.rs hash: 300b4be5841cee6f
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
diff --git a/editors/code/package.json b/editors/code/package.json
index a048862..daabab4 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -171,6 +171,11 @@
                 "category": "rust-analyzer"
             },
             {
+                "command": "rust-analyzer.childrenModules",
+                "title": "Locate children modules",
+                "category": "rust-analyzer"
+            },
+            {
                 "command": "rust-analyzer.joinLines",
                 "title": "Join lines",
                 "category": "rust-analyzer"
@@ -3374,6 +3379,10 @@
                     "when": "inRustProject"
                 },
                 {
+                    "command": "rust-analyzer.childrenModule",
+                    "when": "inRustProject"
+                },
+                {
                     "command": "rust-analyzer.joinLines",
                     "when": "inRustProject"
                 },
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 4e614d3..a78e935 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -266,6 +266,43 @@
     };
 }
 
+export function childrenModules(ctx: CtxInit): Cmd {
+    return async () => {
+        const editor = vscode.window.activeTextEditor;
+        if (!editor) return;
+        if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
+
+        const client = ctx.client;
+
+        const locations = await client.sendRequest(ra.childrenModules, {
+            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
+            position: client.code2ProtocolConverter.asPosition(editor.selection.active),
+        });
+        if (!locations) return;
+
+        if (locations.length === 1) {
+            const loc = unwrapUndefinable(locations[0]);
+
+            const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
+            const range = client.protocol2CodeConverter.asRange(loc.targetRange);
+
+            const doc = await vscode.workspace.openTextDocument(uri);
+            const e = await vscode.window.showTextDocument(doc);
+            e.selection = new vscode.Selection(range.start, range.start);
+            e.revealRange(range, vscode.TextEditorRevealType.InCenter);
+        } else {
+            const uri = editor.document.uri.toString();
+            const position = client.code2ProtocolConverter.asPosition(editor.selection.active);
+            await showReferencesImpl(
+                client,
+                uri,
+                position,
+                locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange)),
+            );
+        }
+    };
+}
+
 export function openCargoToml(ctx: CtxInit): Cmd {
     return async () => {
         const editor = ctx.activeRustEditor;
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index af5129a..1cf55f6 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -194,6 +194,11 @@
     lc.LocationLink[] | null,
     void
 >("experimental/parentModule");
+export const childrenModules = new lc.RequestType<
+    lc.TextDocumentPositionParams,
+    lc.LocationLink[] | null,
+    void
+>("experimental/childrenModule");
 export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
     "experimental/runnables",
 );
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 451294e..95a52d7 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -158,6 +158,7 @@
         matchingBrace: { enabled: commands.matchingBrace },
         joinLines: { enabled: commands.joinLines },
         parentModule: { enabled: commands.parentModule },
+        childrenModules: { enabled: commands.childrenModules },
         viewHir: { enabled: commands.viewHir },
         viewMir: { enabled: commands.viewMir },
         interpretFunction: { enabled: commands.interpretFunction },