diff options
Diffstat (limited to 'crates/ra_ide/src/references')
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 187 |
1 files changed, 139 insertions, 48 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index c4f07f905..99c2581b7 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::{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,49 +97,50 @@ 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 = if mod_path.file_stem() == Some("mod") { | 114 | let dst = if mod_path.file_stem() == Some("mod") { |
96 | format!("../{}/mod.rs", new_name) | 115 | format!("../{}/mod.rs", new_name) |
97 | } else { | 116 | } else { |
98 | format!("{}.rs", new_name) | 117 | format!("{}.rs", new_name) |
99 | }; | 118 | }; |
100 | let move_file = | 119 | let move_file = |
101 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; | 120 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; |
102 | file_system_edits.push(move_file); | 121 | file_system_edits.push(move_file); |
103 | } | ||
104 | ModuleSource::Module(..) => {} | ||
105 | } | 122 | } |
123 | ModuleSource::Module(..) => {} | ||
106 | } | 124 | } |
107 | 125 | ||
108 | let edit = SourceFileEdit { | 126 | if let Some(src) = module.declaration_source(db) { |
109 | file_id: position.file_id, | 127 | let file_id = src.file_id.original_file(db); |
110 | edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), | 128 | let name = src.value.name()?; |
111 | }; | 129 | let edit = SourceFileEdit { |
112 | source_file_edits.push(edit); | 130 | file_id: file_id, |
113 | 131 | 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) { | 132 | }; |
115 | let ref_edits = refs | 133 | 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 | } | 134 | } |
121 | 135 | ||
122 | Some(SourceChange::from_edits(source_file_edits, file_system_edits)) | 136 | let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; |
137 | let ref_edits = refs | ||
138 | .references | ||
139 | .into_iter() | ||
140 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
141 | source_file_edits.extend(ref_edits); | ||
142 | |||
143 | Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | ||
123 | } | 144 | } |
124 | 145 | ||
125 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 146 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { |
@@ -667,6 +688,76 @@ mod foo<|>; | |||
667 | } | 688 | } |
668 | 689 | ||
669 | #[test] | 690 | #[test] |
691 | fn test_rename_mod_in_use_tree() { | ||
692 | let (analysis, position) = analysis_and_position( | ||
693 | r#" | ||
694 | //- /main.rs | ||
695 | pub mod foo; | ||
696 | pub mod bar; | ||
697 | fn main() {} | ||
698 | |||
699 | //- /foo.rs | ||
700 | pub struct FooContent; | ||
701 | |||
702 | //- /bar.rs | ||
703 | use crate::foo<|>::FooContent; | ||
704 | "#, | ||
705 | ); | ||
706 | let new_name = "qux"; | ||
707 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
708 | assert_debug_snapshot!(&source_change, | ||
709 | @r###" | ||
710 | Some( | ||
711 | RangeInfo { | ||
712 | range: 11..14, | ||
713 | info: SourceChange { | ||
714 | source_file_edits: [ | ||
715 | SourceFileEdit { | ||
716 | file_id: FileId( | ||
717 | 1, | ||
718 | ), | ||
719 | edit: TextEdit { | ||
720 | indels: [ | ||
721 | Indel { | ||
722 | insert: "qux", | ||
723 | delete: 8..11, | ||
724 | }, | ||
725 | ], | ||
726 | }, | ||
727 | }, | ||
728 | SourceFileEdit { | ||
729 | file_id: FileId( | ||
730 | 3, | ||
731 | ), | ||
732 | edit: TextEdit { | ||
733 | indels: [ | ||
734 | Indel { | ||
735 | insert: "qux", | ||
736 | delete: 11..14, | ||
737 | }, | ||
738 | ], | ||
739 | }, | ||
740 | }, | ||
741 | ], | ||
742 | file_system_edits: [ | ||
743 | MoveFile { | ||
744 | src: FileId( | ||
745 | 2, | ||
746 | ), | ||
747 | anchor: FileId( | ||
748 | 3, | ||
749 | ), | ||
750 | dst: "qux.rs", | ||
751 | }, | ||
752 | ], | ||
753 | is_snippet: false, | ||
754 | }, | ||
755 | }, | ||
756 | ) | ||
757 | "###); | ||
758 | } | ||
759 | |||
760 | #[test] | ||
670 | fn test_rename_mod_in_dir() { | 761 | fn test_rename_mod_in_dir() { |
671 | let (analysis, position) = analysis_and_position( | 762 | let (analysis, position) = analysis_and_position( |
672 | r#" | 763 | r#" |