Merge pull request #19255 from geetanshjuneja/master

Add children modules feature
This commit is contained in:
Lukas Wirth 2025-04-09 09:53:25 +00:00 committed by GitHub
commit f4747f2617
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 204 additions and 1 deletions

View file

@ -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
"#,
);
}
}

View file

@ -20,6 +20,7 @@ mod navigation_target;
mod annotations; mod annotations;
mod call_hierarchy; mod call_hierarchy;
mod children_modules;
mod doc_links; mod doc_links;
mod expand_macro; mod expand_macro;
mod extend_selection; mod extend_selection;
@ -605,6 +606,11 @@ impl Analysis {
self.with_db(|db| parent_module::parent_module(db, position)) 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. /// Returns crates that this file belongs to.
pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<Crate>> { pub fn crates_for(&self, file_id: FileId) -> Cancellable<Vec<Crate>> {
self.with_db(|db| parent_module::crates_for(db, file_id)) self.with_db(|db| parent_module::crates_for(db, file_id))

View file

@ -943,6 +943,18 @@ pub(crate) fn handle_parent_module(
Ok(Some(res)) 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( pub(crate) fn handle_runnables(
snap: GlobalStateSnapshot, snap: GlobalStateSnapshot,
params: lsp_ext::RunnablesParams, params: lsp_ext::RunnablesParams,

View file

@ -157,6 +157,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
"onEnter": true, "onEnter": true,
"openCargoToml": true, "openCargoToml": true,
"parentModule": true, "parentModule": true,
"childrenModules": true,
"runnables": { "runnables": {
"kinds": [ "cargo" ], "kinds": [ "cargo" ],
}, },

View file

@ -399,6 +399,14 @@ impl Request for ParentModule {
const METHOD: &'static str = "experimental/parentModule"; 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 {} pub enum JoinLines {}
impl Request for JoinLines { impl Request for JoinLines {

View file

@ -1172,6 +1172,7 @@ impl GlobalState {
.on::<NO_RETRY, lsp_ext::InterpretFunction>(handlers::handle_interpret_function) .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::ExpandMacro>(handlers::handle_expand_macro)
.on::<NO_RETRY, lsp_ext::ParentModule>(handlers::handle_parent_module) .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::Runnables>(handlers::handle_runnables)
.on::<NO_RETRY, lsp_ext::RelatedTests>(handlers::handle_related_tests) .on::<NO_RETRY, lsp_ext::RelatedTests>(handlers::handle_related_tests)
.on::<NO_RETRY, lsp_ext::CodeActionRequest>(handlers::handle_code_action) .on::<NO_RETRY, lsp_ext::CodeActionRequest>(handlers::handle_code_action)

View file

@ -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 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: need to adjust this doc as well and ping this issue:

View file

@ -170,6 +170,11 @@
"title": "Locate parent module", "title": "Locate parent module",
"category": "rust-analyzer" "category": "rust-analyzer"
}, },
{
"command": "rust-analyzer.childrenModules",
"title": "Locate children modules",
"category": "rust-analyzer"
},
{ {
"command": "rust-analyzer.joinLines", "command": "rust-analyzer.joinLines",
"title": "Join lines", "title": "Join lines",
@ -3373,6 +3378,10 @@
"command": "rust-analyzer.parentModule", "command": "rust-analyzer.parentModule",
"when": "inRustProject" "when": "inRustProject"
}, },
{
"command": "rust-analyzer.childrenModule",
"when": "inRustProject"
},
{ {
"command": "rust-analyzer.joinLines", "command": "rust-analyzer.joinLines",
"when": "inRustProject" "when": "inRustProject"

View file

@ -266,6 +266,43 @@ export function parentModule(ctx: CtxInit): Cmd {
}; };
} }
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 { export function openCargoToml(ctx: CtxInit): Cmd {
return async () => { return async () => {
const editor = ctx.activeRustEditor; const editor = ctx.activeRustEditor;

View file

@ -194,6 +194,11 @@ export const parentModule = new lc.RequestType<
lc.LocationLink[] | null, lc.LocationLink[] | null,
void void
>("experimental/parentModule"); >("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>( export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
"experimental/runnables", "experimental/runnables",
); );

View file

@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
matchingBrace: { enabled: commands.matchingBrace }, matchingBrace: { enabled: commands.matchingBrace },
joinLines: { enabled: commands.joinLines }, joinLines: { enabled: commands.joinLines },
parentModule: { enabled: commands.parentModule }, parentModule: { enabled: commands.parentModule },
childrenModules: { enabled: commands.childrenModules },
viewHir: { enabled: commands.viewHir }, viewHir: { enabled: commands.viewHir },
viewMir: { enabled: commands.viewMir }, viewMir: { enabled: commands.viewMir },
interpretFunction: { enabled: commands.interpretFunction }, interpretFunction: { enabled: commands.interpretFunction },