Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #12387 - 00nktk:fix-mod-rename, r=Veykril
fix(ide-db): correct single-file module rename
Fixes a bug where rust-analyzer would emit `WorkspaceEdit`s with paths to dirs instead of files for the following project layout.
lib.rs
```rust
mod foo;
```
foo.rs
```rust
mod bar {
struct Bar;
}
```
Also fixes emitted paths for modules with mod.rs.
The bug resulted in panic in helix editor when attempting to rename a module.
| -rw-r--r-- | crates/hir/src/has_source.rs | 5 | ||||
| -rw-r--r-- | crates/ide-db/src/rename.rs | 30 | ||||
| -rw-r--r-- | crates/ide/src/rename.rs | 15 |
3 files changed, 42 insertions, 8 deletions
diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index 037f51ec8e..f8b01db3e3 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -39,6 +39,11 @@ impl Module { } } + pub fn is_inline(self, db: &dyn HirDatabase) -> bool { + let def_map = self.id.def_map(db.upcast()); + def_map[self.id.local_id].origin.is_inline() + } + /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. /// `None` for the crate root. pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> { diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 589975406a..8f83496e93 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -180,15 +180,33 @@ fn rename_mod( let InFile { file_id, value: def_source } = module.definition_source(sema.db); if let ModuleSource::SourceFile(..) = def_source { let anchor = file_id.original_file(sema.db); - // not mod.rs and doesn't has children, rename file only - if !module.is_mod_rs(sema.db) && module.children(sema.db).next().is_none() { + + let is_mod_rs = module.is_mod_rs(sema.db); + let has_detached_child = module.children(sema.db).any(|child| !child.is_inline(sema.db)); + + // Module exists in a named file + if !is_mod_rs { let path = format!("{}.rs", new_name); let dst = AnchoredPathBuf { anchor, path }; source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst }) - } else if let Some(mod_name) = module.name(sema.db) { - // is mod.rs or has children, rename dir - let src = AnchoredPathBuf { anchor, path: mod_name.to_string() }; - let dst = AnchoredPathBuf { anchor, path: new_name.to_string() }; + } + + // Rename the dir if: + // - Module source is in mod.rs + // - Module has submodules defined in separate files + let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) { + // Go up one level since the anchor is inside the dir we're trying to rename + (true, _, Some(mod_name)) => { + Some((format!("../{}", mod_name), format!("../{}", new_name))) + } + // The anchor is on the same level as target dir + (false, true, Some(mod_name)) => Some((mod_name.to_string(), new_name.to_string())), + _ => None, + }; + + if let Some((src, dst)) = dir_paths { + let src = AnchoredPathBuf { anchor, path: src }; + let dst = AnchoredPathBuf { anchor, path: dst }; source_change.push_file_system_edit(FileSystemEdit::MoveDir { src, src_id: anchor, diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index f5bff5fd91..0f8033611a 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -973,7 +973,7 @@ mod fo$0o; anchor: FileId( 1, ), - path: "foo", + path: "../foo", }, src_id: FileId( 1, @@ -982,7 +982,7 @@ mod fo$0o; anchor: FileId( 1, ), - path: "foo2", + path: "../foo2", }, }, ], @@ -1158,6 +1158,17 @@ mod quux; }, }, file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "foo2.rs", + }, + }, MoveDir { src: AnchoredPathBuf { anchor: FileId( |