diff options
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 201 |
1 files changed, 146 insertions, 55 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 915d4f4d3..546224b50 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -1,11 +1,14 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{ModuleSource, Semantics}; | 3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | ||
7 | RootDatabase, | ||
8 | }; | ||
6 | use ra_syntax::{ | 9 | use ra_syntax::{ |
7 | algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, | 10 | algo::find_node_at_offset, ast, ast::NameOwner, ast::TypeAscriptionOwner, |
8 | AstNode, SyntaxKind, SyntaxNode, SyntaxToken, | 11 | lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, |
9 | }; | 12 | }; |
10 | use ra_text_edit::TextEdit; | 13 | use ra_text_edit::TextEdit; |
11 | use std::convert::TryInto; | 14 | use std::convert::TryInto; |
@@ -30,10 +33,8 @@ pub(crate) fn rename( | |||
30 | let sema = Semantics::new(db); | 33 | let sema = Semantics::new(db); |
31 | let source_file = sema.parse(position.file_id); | 34 | let source_file = sema.parse(position.file_id); |
32 | let syntax = source_file.syntax(); | 35 | let syntax = source_file.syntax(); |
33 | if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { | 36 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
34 | let range = ast_name.syntax().text_range(); | 37 | rename_mod(db, position, module, new_name) |
35 | rename_mod(&sema, &ast_name, &ast_module, position, new_name) | ||
36 | .map(|info| RangeInfo::new(range, info)) | ||
37 | } else if let Some(self_token) = | 38 | } else if let Some(self_token) = |
38 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 39 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) |
39 | { | 40 | { |
@@ -43,13 +44,32 @@ pub(crate) fn rename( | |||
43 | } | 44 | } |
44 | } | 45 | } |
45 | 46 | ||
46 | fn find_name_and_module_at_offset( | 47 | fn find_module_at_offset( |
47 | syntax: &SyntaxNode, | 48 | sema: &Semantics<RootDatabase>, |
48 | position: FilePosition, | 49 | position: FilePosition, |
49 | ) -> Option<(ast::Name, ast::Module)> { | 50 | syntax: &SyntaxNode, |
50 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; | 51 | ) -> Option<Module> { |
51 | let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; | 52 | let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; |
52 | Some((ast_name, ast_module)) | 53 | |
54 | let module = match_ast! { | ||
55 | match (ident.parent()) { | ||
56 | ast::NameRef(name_ref) => { | ||
57 | match classify_name_ref(sema, &name_ref)? { | ||
58 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
59 | _ => return None, | ||
60 | } | ||
61 | }, | ||
62 | ast::Name(name) => { | ||
63 | match classify_name(&sema, &name)? { | ||
64 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
65 | _ => return None, | ||
66 | } | ||
67 | }, | ||
68 | _ => return None, | ||
69 | } | ||
70 | }; | ||
71 | |||
72 | Some(module) | ||
53 | } | 73 | } |
54 | 74 | ||
55 | fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { | 75 | fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { |
@@ -77,58 +97,59 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil | |||
77 | } | 97 | } |
78 | 98 | ||
79 | fn rename_mod( | 99 | fn rename_mod( |
80 | sema: &Semantics<RootDatabase>, | 100 | db: &RootDatabase, |
81 | ast_name: &ast::Name, | ||
82 | ast_module: &ast::Module, | ||
83 | position: FilePosition, | 101 | position: FilePosition, |
102 | module: Module, | ||
84 | new_name: &str, | 103 | new_name: &str, |
85 | ) -> Option<SourceChange> { | 104 | ) -> Option<RangeInfo<SourceChange>> { |
86 | let mut source_file_edits = Vec::new(); | 105 | let mut source_file_edits = Vec::new(); |
87 | let mut file_system_edits = Vec::new(); | 106 | let mut file_system_edits = Vec::new(); |
88 | if let Some(module) = sema.to_def(ast_module) { | 107 | |
89 | let src = module.definition_source(sema.db); | 108 | let src = module.definition_source(db); |
90 | let file_id = src.file_id.original_file(sema.db); | 109 | let file_id = src.file_id.original_file(db); |
91 | match src.value { | 110 | match src.value { |
92 | ModuleSource::SourceFile(..) => { | 111 | ModuleSource::SourceFile(..) => { |
93 | let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); | 112 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); |
94 | // mod is defined in path/to/dir/mod.rs | 113 | // mod is defined in path/to/dir/mod.rs |
95 | let dst_path = if mod_path.file_stem() == Some("mod") { | 114 | let dst_path = if mod_path.file_stem() == Some("mod") { |
96 | mod_path | 115 | mod_path |
97 | .parent() | 116 | .parent() |
98 | .and_then(|p| p.parent()) | 117 | .and_then(|p| p.parent()) |
99 | .or_else(|| Some(RelativePath::new(""))) | 118 | .or_else(|| Some(RelativePath::new(""))) |
100 | .map(|p| p.join(new_name).join("mod.rs")) | 119 | .map(|p| p.join(new_name).join("mod.rs")) |
101 | } else { | 120 | } else { |
102 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | 121 | Some(mod_path.with_file_name(new_name).with_extension("rs")) |
122 | }; | ||
123 | if let Some(path) = dst_path { | ||
124 | let move_file = FileSystemEdit::MoveFile { | ||
125 | src: file_id, | ||
126 | dst_source_root: db.file_source_root(position.file_id), | ||
127 | dst_path: path, | ||
103 | }; | 128 | }; |
104 | if let Some(path) = dst_path { | 129 | file_system_edits.push(move_file); |
105 | let move_file = FileSystemEdit::MoveFile { | ||
106 | src: file_id, | ||
107 | dst_source_root: sema.db.file_source_root(position.file_id), | ||
108 | dst_path: path, | ||
109 | }; | ||
110 | file_system_edits.push(move_file); | ||
111 | } | ||
112 | } | 130 | } |
113 | ModuleSource::Module(..) => {} | ||
114 | } | 131 | } |
132 | ModuleSource::Module(..) => {} | ||
115 | } | 133 | } |
116 | 134 | ||
117 | let edit = SourceFileEdit { | 135 | if let Some(src) = module.declaration_source(db) { |
118 | file_id: position.file_id, | 136 | let file_id = src.file_id.original_file(db); |
119 | edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), | 137 | let name = src.value.name()?; |
120 | }; | 138 | let edit = SourceFileEdit { |
121 | source_file_edits.push(edit); | 139 | file_id: file_id, |
122 | 140 | edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), | |
123 | if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { | 141 | }; |
124 | let ref_edits = refs | 142 | source_file_edits.push(edit); |
125 | .references | ||
126 | .into_iter() | ||
127 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
128 | source_file_edits.extend(ref_edits); | ||
129 | } | 143 | } |
130 | 144 | ||
131 | Some(SourceChange::from_edits(source_file_edits, file_system_edits)) | 145 | let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; |
146 | let ref_edits = refs | ||
147 | .references | ||
148 | .into_iter() | ||
149 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
150 | source_file_edits.extend(ref_edits); | ||
151 | |||
152 | Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | ||
132 | } | 153 | } |
133 | 154 | ||
134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 155 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { |
@@ -676,6 +697,76 @@ mod tests { | |||
676 | } | 697 | } |
677 | 698 | ||
678 | #[test] | 699 | #[test] |
700 | fn test_rename_mod_in_use_tree() { | ||
701 | let (analysis, position) = analysis_and_position( | ||
702 | " | ||
703 | //- /main.rs | ||
704 | pub mod foo; | ||
705 | pub mod bar; | ||
706 | fn main() {} | ||
707 | |||
708 | //- /foo.rs | ||
709 | pub struct FooContent; | ||
710 | |||
711 | //- /bar.rs | ||
712 | use crate::foo<|>::FooContent; | ||
713 | ", | ||
714 | ); | ||
715 | let new_name = "qux"; | ||
716 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
717 | assert_debug_snapshot!(&source_change, | ||
718 | @r###" | ||
719 | Some( | ||
720 | RangeInfo { | ||
721 | range: 11..14, | ||
722 | info: SourceChange { | ||
723 | source_file_edits: [ | ||
724 | SourceFileEdit { | ||
725 | file_id: FileId( | ||
726 | 1, | ||
727 | ), | ||
728 | edit: TextEdit { | ||
729 | indels: [ | ||
730 | Indel { | ||
731 | insert: "qux", | ||
732 | delete: 8..11, | ||
733 | }, | ||
734 | ], | ||
735 | }, | ||
736 | }, | ||
737 | SourceFileEdit { | ||
738 | file_id: FileId( | ||
739 | 3, | ||
740 | ), | ||
741 | edit: TextEdit { | ||
742 | indels: [ | ||
743 | Indel { | ||
744 | insert: "qux", | ||
745 | delete: 11..14, | ||
746 | }, | ||
747 | ], | ||
748 | }, | ||
749 | }, | ||
750 | ], | ||
751 | file_system_edits: [ | ||
752 | MoveFile { | ||
753 | src: FileId( | ||
754 | 2, | ||
755 | ), | ||
756 | dst_source_root: SourceRootId( | ||
757 | 0, | ||
758 | ), | ||
759 | dst_path: "qux.rs", | ||
760 | }, | ||
761 | ], | ||
762 | is_snippet: false, | ||
763 | }, | ||
764 | }, | ||
765 | ) | ||
766 | "###); | ||
767 | } | ||
768 | |||
769 | #[test] | ||
679 | fn test_rename_mod_in_dir() { | 770 | fn test_rename_mod_in_dir() { |
680 | let (analysis, position) = analysis_and_position( | 771 | let (analysis, position) = analysis_and_position( |
681 | " | 772 | " |