diff --git a/crates/ide_assists/src/handlers/move_module_to_file.rs b/crates/ide_assists/src/handlers/move_module_to_file.rs index 93f702c556..cfc54be8d1 100644 --- a/crates/ide_assists/src/handlers/move_module_to_file.rs +++ b/crates/ide_assists/src/handlers/move_module_to_file.rs @@ -1,5 +1,8 @@ +use std::iter; + use ast::edit::IndentLevel; use ide_db::base_db::AnchoredPathBuf; +use itertools::Itertools; use stdx::format_to; use syntax::{ ast::{self, edit::AstNodeEdit, NameOwner}, @@ -34,7 +37,10 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Opt let module_name = module_ast.name()?; - let module_def = ctx.sema.to_def(&module_ast)?; + // get to the outermost module syntax so we can grab the module of file we are in + let outermost_mod_decl = + iter::successors(Some(module_ast.clone()), |module| module.parent()).last()?; + let module_def = ctx.sema.to_def(&outermost_mod_decl)?; let parent_module = module_def.parent(ctx.db())?; acc.add( @@ -43,11 +49,19 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Opt target, |builder| { let path = { - let dir = match parent_module.name(ctx.db()) { - Some(name) if !parent_module.is_mod_rs(ctx.db()) => format!("{}/", name), - _ => String::new(), - }; - format!("./{}{}.rs", dir, module_name) + let mut buf = String::from("./"); + match parent_module.name(ctx.db()) { + Some(name) if !parent_module.is_mod_rs(ctx.db()) => { + format_to!(buf, "{}/", name) + } + _ => (), + } + let segments = iter::successors(Some(module_ast.clone()), |module| module.parent()) + .filter_map(|it| it.name()) + .collect::>(); + format_to!(buf, "{}", segments.into_iter().rev().format("/")); + format_to!(buf, ".rs"); + buf }; let contents = { let items = module_items.dedent(IndentLevel(1)).to_string(); @@ -59,14 +73,13 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Opt items }; - let mut buf = String::new(); - format_to!(buf, "mod {};", module_name); + let buf = format!("mod {};", module_name); - let replacement_start = if let Some(mod_token) = module_ast.mod_token() { - mod_token.text_range().start() - } else { - module_ast.syntax().text_range().start() - }; + let replacement_start = match module_ast.mod_token() { + Some(mod_token) => mod_token.text_range(), + None => module_ast.syntax().text_range(), + } + .start(); builder.replace( TextRange::new(replacement_start, module_ast.syntax().text_range().end()), @@ -209,6 +222,32 @@ mod $0tests { mod tests; //- /tests.rs #[test] fn t() {} +"#, + ); + } + + #[test] + fn extract_nested() { + check_assist( + move_module_to_file, + r#" +//- /lib.rs +mod foo; +//- /foo.rs +mod bar { + mod baz { + mod qux$0 {} + } +} +"#, + r#" +//- /foo.rs +mod bar { + mod baz { + mod qux; + } +} +//- /foo/bar/baz/qux.rs "#, ); } diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 2bd9ad867e..b057e66240 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -675,6 +675,14 @@ impl ast::LifetimeParam { } } +impl ast::Module { + /// Returns the parent ast::Module, this is different than the semantic parent in that this only + /// considers parent declarations in the AST + pub fn parent(&self) -> Option { + self.syntax().ancestors().nth(2).and_then(ast::Module::cast) + } +} + impl ast::RangePat { pub fn start(&self) -> Option { self.syntax()