diff options
Diffstat (limited to 'crates/ra_ide/src/references/rename.rs')
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 202 |
1 files changed, 146 insertions, 56 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index c4f07f905..7ebc0adcf 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::{RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::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,49 +97,49 @@ 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 | // mod is defined in path/to/dir/mod.rs |
94 | // mod is defined in path/to/dir/mod.rs | 113 | let dst = if module.is_mod_rs(db) { |
95 | let dst = if mod_path.file_stem() == Some("mod") { | 114 | format!("../{}/mod.rs", new_name) |
96 | format!("../{}/mod.rs", new_name) | 115 | } else { |
97 | } else { | 116 | format!("{}.rs", new_name) |
98 | format!("{}.rs", new_name) | 117 | }; |
99 | }; | 118 | let move_file = |
100 | let move_file = | 119 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; |
101 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; | 120 | file_system_edits.push(move_file); |
102 | file_system_edits.push(move_file); | ||
103 | } | ||
104 | ModuleSource::Module(..) => {} | ||
105 | } | 121 | } |
122 | ModuleSource::Module(..) => {} | ||
106 | } | 123 | } |
107 | 124 | ||
108 | let edit = SourceFileEdit { | 125 | if let Some(src) = module.declaration_source(db) { |
109 | file_id: position.file_id, | 126 | let file_id = src.file_id.original_file(db); |
110 | edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), | 127 | let name = src.value.name()?; |
111 | }; | 128 | let edit = SourceFileEdit { |
112 | source_file_edits.push(edit); | 129 | file_id: file_id, |
113 | 130 | edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), | |
114 | if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { | 131 | }; |
115 | let ref_edits = refs | 132 | source_file_edits.push(edit); |
116 | .references | ||
117 | .into_iter() | ||
118 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
119 | source_file_edits.extend(ref_edits); | ||
120 | } | 133 | } |
121 | 134 | ||
122 | Some(SourceChange::from_edits(source_file_edits, file_system_edits)) | 135 | let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; |
136 | let ref_edits = refs | ||
137 | .references | ||
138 | .into_iter() | ||
139 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
140 | source_file_edits.extend(ref_edits); | ||
141 | |||
142 | Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | ||
123 | } | 143 | } |
124 | 144 | ||
125 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 145 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { |
@@ -251,11 +271,10 @@ fn rename_reference( | |||
251 | mod tests { | 271 | mod tests { |
252 | use insta::assert_debug_snapshot; | 272 | use insta::assert_debug_snapshot; |
253 | use ra_text_edit::TextEditBuilder; | 273 | use ra_text_edit::TextEditBuilder; |
274 | use stdx::trim_indent; | ||
254 | use test_utils::{assert_eq_text, mark}; | 275 | use test_utils::{assert_eq_text, mark}; |
255 | 276 | ||
256 | use crate::{ | 277 | use crate::{mock_analysis::analysis_and_position, FileId}; |
257 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | ||
258 | }; | ||
259 | 278 | ||
260 | #[test] | 279 | #[test] |
261 | fn test_rename_to_underscore() { | 280 | fn test_rename_to_underscore() { |
@@ -289,7 +308,7 @@ mod tests { | |||
289 | 308 | ||
290 | #[test] | 309 | #[test] |
291 | fn test_rename_to_invalid_identifier() { | 310 | fn test_rename_to_invalid_identifier() { |
292 | let (analysis, position) = single_file_with_position( | 311 | let (analysis, position) = analysis_and_position( |
293 | " | 312 | " |
294 | fn main() { | 313 | fn main() { |
295 | let i<|> = 1; | 314 | let i<|> = 1; |
@@ -667,6 +686,76 @@ mod foo<|>; | |||
667 | } | 686 | } |
668 | 687 | ||
669 | #[test] | 688 | #[test] |
689 | fn test_rename_mod_in_use_tree() { | ||
690 | let (analysis, position) = analysis_and_position( | ||
691 | r#" | ||
692 | //- /main.rs | ||
693 | pub mod foo; | ||
694 | pub mod bar; | ||
695 | fn main() {} | ||
696 | |||
697 | //- /foo.rs | ||
698 | pub struct FooContent; | ||
699 | |||
700 | //- /bar.rs | ||
701 | use crate::foo<|>::FooContent; | ||
702 | "#, | ||
703 | ); | ||
704 | let new_name = "qux"; | ||
705 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
706 | assert_debug_snapshot!(&source_change, | ||
707 | @r###" | ||
708 | Some( | ||
709 | RangeInfo { | ||
710 | range: 11..14, | ||
711 | info: SourceChange { | ||
712 | source_file_edits: [ | ||
713 | SourceFileEdit { | ||
714 | file_id: FileId( | ||
715 | 1, | ||
716 | ), | ||
717 | edit: TextEdit { | ||
718 | indels: [ | ||
719 | Indel { | ||
720 | insert: "qux", | ||
721 | delete: 8..11, | ||
722 | }, | ||
723 | ], | ||
724 | }, | ||
725 | }, | ||
726 | SourceFileEdit { | ||
727 | file_id: FileId( | ||
728 | 3, | ||
729 | ), | ||
730 | edit: TextEdit { | ||
731 | indels: [ | ||
732 | Indel { | ||
733 | insert: "qux", | ||
734 | delete: 11..14, | ||
735 | }, | ||
736 | ], | ||
737 | }, | ||
738 | }, | ||
739 | ], | ||
740 | file_system_edits: [ | ||
741 | MoveFile { | ||
742 | src: FileId( | ||
743 | 2, | ||
744 | ), | ||
745 | anchor: FileId( | ||
746 | 3, | ||
747 | ), | ||
748 | dst: "qux.rs", | ||
749 | }, | ||
750 | ], | ||
751 | is_snippet: false, | ||
752 | }, | ||
753 | }, | ||
754 | ) | ||
755 | "###); | ||
756 | } | ||
757 | |||
758 | #[test] | ||
670 | fn test_rename_mod_in_dir() { | 759 | fn test_rename_mod_in_dir() { |
671 | let (analysis, position) = analysis_and_position( | 760 | let (analysis, position) = analysis_and_position( |
672 | r#" | 761 | r#" |
@@ -963,8 +1052,9 @@ pub mod foo<|>; | |||
963 | ); | 1052 | ); |
964 | } | 1053 | } |
965 | 1054 | ||
966 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 1055 | fn test_rename(ra_fixture_before: &str, new_name: &str, ra_fixture_after: &str) { |
967 | let (analysis, position) = single_file_with_position(text); | 1056 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
1057 | let (analysis, position) = analysis_and_position(ra_fixture_before); | ||
968 | let source_change = analysis.rename(position, new_name).unwrap(); | 1058 | let source_change = analysis.rename(position, new_name).unwrap(); |
969 | let mut text_edit_builder = TextEditBuilder::default(); | 1059 | let mut text_edit_builder = TextEditBuilder::default(); |
970 | let mut file_id: Option<FileId> = None; | 1060 | let mut file_id: Option<FileId> = None; |
@@ -978,6 +1068,6 @@ pub mod foo<|>; | |||
978 | } | 1068 | } |
979 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); | 1069 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); |
980 | text_edit_builder.finish().apply(&mut result); | 1070 | text_edit_builder.finish().apply(&mut result); |
981 | assert_eq_text!(expected, &*result); | 1071 | assert_eq_text!(ra_fixture_after, &*result); |
982 | } | 1072 | } |
983 | } | 1073 | } |