Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add children modules feature #19255

Merged
merged 2 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions crates/ide/src/children_modules.rs
Original file line number Diff line number Diff line change
@@ -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
"#,
);
}
}
6 changes: 6 additions & 0 deletions crates/ide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod navigation_target;

mod annotations;
mod call_hierarchy;
mod children_modules;
mod doc_links;
mod expand_macro;
mod extend_selection;
Expand Down Expand Up @@ -605,6 +606,11 @@ impl Analysis {
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))
Expand Down
12 changes: 12 additions & 0 deletions crates/rust-analyzer/src/handlers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,18 @@ pub(crate) fn handle_parent_module(
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,
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/lsp/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
"onEnter": true,
"openCargoToml": true,
"parentModule": true,
"childrenModules": true,
"runnables": {
"kinds": [ "cargo" ],
},
Expand Down
8 changes: 8 additions & 0 deletions crates/rust-analyzer/src/lsp/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,14 @@ impl Request for 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 {}

impl Request for JoinLines {
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,7 @@ impl GlobalState {
.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)
Expand Down
2 changes: 1 addition & 1 deletion docs/book/src/contributing/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
9 changes: 9 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@
"title": "Locate parent module",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.childrenModules",
"title": "Locate children modules",
"category": "rust-analyzer"
},
{
"command": "rust-analyzer.joinLines",
"title": "Join lines",
Expand Down Expand Up @@ -3373,6 +3378,10 @@
"command": "rust-analyzer.parentModule",
"when": "inRustProject"
},
{
"command": "rust-analyzer.childrenModule",
"when": "inRustProject"
},
{
"command": "rust-analyzer.joinLines",
"when": "inRustProject"
Expand Down
37 changes: 37 additions & 0 deletions editors/code/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
return async () => {
const editor = ctx.activeRustEditor;
Expand Down
5 changes: 5 additions & 0 deletions editors/code/src/lsp_ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export const parentModule = new lc.RequestType<
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",
);
Expand Down
1 change: 1 addition & 0 deletions editors/code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
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 },
Expand Down