diff options
Diffstat (limited to 'crates/ra_ide/src/references')
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 266 |
1 files changed, 174 insertions, 92 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 915d4f4d3..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::{RelativePath, 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,58 +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_path = if mod_path.file_stem() == Some("mod") { | 114 | let dst = if mod_path.file_stem() == Some("mod") { |
96 | mod_path | 115 | format!("../{}/mod.rs", new_name) |
97 | .parent() | 116 | } else { |
98 | .and_then(|p| p.parent()) | 117 | format!("{}.rs", new_name) |
99 | .or_else(|| Some(RelativePath::new(""))) | 118 | }; |
100 | .map(|p| p.join(new_name).join("mod.rs")) | 119 | let move_file = |
101 | } else { | 120 | FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; |
102 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | 121 | file_system_edits.push(move_file); |
103 | }; | ||
104 | if let Some(path) = dst_path { | ||
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 | } | ||
113 | ModuleSource::Module(..) => {} | ||
114 | } | 122 | } |
123 | ModuleSource::Module(..) => {} | ||
115 | } | 124 | } |
116 | 125 | ||
117 | let edit = SourceFileEdit { | 126 | if let Some(src) = module.declaration_source(db) { |
118 | file_id: position.file_id, | 127 | let file_id = src.file_id.original_file(db); |
119 | edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), | 128 | let name = src.value.name()?; |
120 | }; | 129 | let edit = SourceFileEdit { |
121 | source_file_edits.push(edit); | 130 | file_id: file_id, |
122 | 131 | 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) { | 132 | }; |
124 | let ref_edits = refs | 133 | 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 | } | 134 | } |
130 | 135 | ||
131 | 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))) | ||
132 | } | 144 | } |
133 | 145 | ||
134 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { | 146 | fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { |
@@ -623,16 +635,16 @@ mod tests { | |||
623 | #[test] | 635 | #[test] |
624 | fn test_rename_mod() { | 636 | fn test_rename_mod() { |
625 | let (analysis, position) = analysis_and_position( | 637 | let (analysis, position) = analysis_and_position( |
626 | " | 638 | r#" |
627 | //- /lib.rs | 639 | //- /lib.rs |
628 | mod bar; | 640 | mod bar; |
629 | 641 | ||
630 | //- /bar.rs | 642 | //- /bar.rs |
631 | mod foo<|>; | 643 | mod foo<|>; |
632 | 644 | ||
633 | //- /bar/foo.rs | 645 | //- /bar/foo.rs |
634 | // emtpy | 646 | // emtpy |
635 | ", | 647 | "#, |
636 | ); | 648 | ); |
637 | let new_name = "foo2"; | 649 | let new_name = "foo2"; |
638 | let source_change = analysis.rename(position, new_name).unwrap(); | 650 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -662,10 +674,80 @@ mod tests { | |||
662 | src: FileId( | 674 | src: FileId( |
663 | 3, | 675 | 3, |
664 | ), | 676 | ), |
665 | dst_source_root: SourceRootId( | 677 | anchor: FileId( |
666 | 0, | 678 | 2, |
667 | ), | 679 | ), |
668 | dst_path: "bar/foo2.rs", | 680 | dst: "foo2.rs", |
681 | }, | ||
682 | ], | ||
683 | is_snippet: false, | ||
684 | }, | ||
685 | }, | ||
686 | ) | ||
687 | "###); | ||
688 | } | ||
689 | |||
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", | ||
669 | }, | 751 | }, |
670 | ], | 752 | ], |
671 | is_snippet: false, | 753 | is_snippet: false, |
@@ -678,12 +760,12 @@ mod tests { | |||
678 | #[test] | 760 | #[test] |
679 | fn test_rename_mod_in_dir() { | 761 | fn test_rename_mod_in_dir() { |
680 | let (analysis, position) = analysis_and_position( | 762 | let (analysis, position) = analysis_and_position( |
681 | " | 763 | r#" |
682 | //- /lib.rs | 764 | //- /lib.rs |
683 | mod fo<|>o; | 765 | mod fo<|>o; |
684 | //- /foo/mod.rs | 766 | //- /foo/mod.rs |
685 | // emtpy | 767 | // emtpy |
686 | ", | 768 | "#, |
687 | ); | 769 | ); |
688 | let new_name = "foo2"; | 770 | let new_name = "foo2"; |
689 | let source_change = analysis.rename(position, new_name).unwrap(); | 771 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -713,10 +795,10 @@ mod tests { | |||
713 | src: FileId( | 795 | src: FileId( |
714 | 2, | 796 | 2, |
715 | ), | 797 | ), |
716 | dst_source_root: SourceRootId( | 798 | anchor: FileId( |
717 | 0, | 799 | 1, |
718 | ), | 800 | ), |
719 | dst_path: "foo2/mod.rs", | 801 | dst: "../foo2/mod.rs", |
720 | }, | 802 | }, |
721 | ], | 803 | ], |
722 | is_snippet: false, | 804 | is_snippet: false, |
@@ -753,19 +835,19 @@ mod tests { | |||
753 | #[test] | 835 | #[test] |
754 | fn test_rename_mod_filename_and_path() { | 836 | fn test_rename_mod_filename_and_path() { |
755 | let (analysis, position) = analysis_and_position( | 837 | let (analysis, position) = analysis_and_position( |
756 | " | 838 | r#" |
757 | //- /lib.rs | 839 | //- /lib.rs |
758 | mod bar; | 840 | mod bar; |
759 | fn f() { | 841 | fn f() { |
760 | bar::foo::fun() | 842 | bar::foo::fun() |
761 | } | 843 | } |
762 | 844 | ||
763 | //- /bar.rs | 845 | //- /bar.rs |
764 | pub mod foo<|>; | 846 | pub mod foo<|>; |
765 | 847 | ||
766 | //- /bar/foo.rs | 848 | //- /bar/foo.rs |
767 | // pub fn fun() {} | 849 | // pub fn fun() {} |
768 | ", | 850 | "#, |
769 | ); | 851 | ); |
770 | let new_name = "foo2"; | 852 | let new_name = "foo2"; |
771 | let source_change = analysis.rename(position, new_name).unwrap(); | 853 | let source_change = analysis.rename(position, new_name).unwrap(); |
@@ -808,10 +890,10 @@ mod tests { | |||
808 | src: FileId( | 890 | src: FileId( |
809 | 3, | 891 | 3, |
810 | ), | 892 | ), |
811 | dst_source_root: SourceRootId( | 893 | anchor: FileId( |
812 | 0, | 894 | 2, |
813 | ), | 895 | ), |
814 | dst_path: "bar/foo2.rs", | 896 | dst: "foo2.rs", |
815 | }, | 897 | }, |
816 | ], | 898 | ], |
817 | is_snippet: false, | 899 | is_snippet: false, |