diff options
author | Aleksey Kladov <[email protected]> | 2019-02-08 10:51:22 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-02-08 11:34:30 +0000 |
commit | 4d0e58afef1722d5f5bf5970bed44594c27ecf34 (patch) | |
tree | bf66b8623925e58d29f7b331d834e25203af0b6d /crates/ra_ide_api/src/references.rs | |
parent | bddd1242986f3155bdb1ca65495bc0623e3d211d (diff) |
rename rename to references
Diffstat (limited to 'crates/ra_ide_api/src/references.rs')
-rw-r--r-- | crates/ra_ide_api/src/references.rs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs new file mode 100644 index 000000000..1c9491a0a --- /dev/null +++ b/crates/ra_ide_api/src/references.rs | |||
@@ -0,0 +1,273 @@ | |||
1 | use relative_path::RelativePathBuf; | ||
2 | |||
3 | use hir::{ | ||
4 | self, ModuleSource, source_binder::module_from_declaration, | ||
5 | }; | ||
6 | use ra_syntax::{ | ||
7 | algo::find_node_at_offset, | ||
8 | ast, | ||
9 | AstNode, | ||
10 | SyntaxNode | ||
11 | }; | ||
12 | |||
13 | use crate::{ | ||
14 | db::RootDatabase, | ||
15 | FilePosition, | ||
16 | FileSystemEdit, | ||
17 | SourceChange, | ||
18 | SourceFileEdit, | ||
19 | }; | ||
20 | use ra_db::SourceDatabase; | ||
21 | use relative_path::RelativePath; | ||
22 | |||
23 | pub(crate) fn rename( | ||
24 | db: &RootDatabase, | ||
25 | position: FilePosition, | ||
26 | new_name: &str, | ||
27 | ) -> Option<SourceChange> { | ||
28 | let source_file = db.parse(position.file_id); | ||
29 | let syntax = source_file.syntax(); | ||
30 | |||
31 | if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { | ||
32 | rename_mod(db, ast_name, ast_module, position, new_name) | ||
33 | } else { | ||
34 | rename_reference(db, position, new_name) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | fn find_name_and_module_at_offset( | ||
39 | syntax: &SyntaxNode, | ||
40 | position: FilePosition, | ||
41 | ) -> Option<(&ast::Name, &ast::Module)> { | ||
42 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset); | ||
43 | let ast_name_parent = ast::Module::cast(ast_name?.syntax().parent()?); | ||
44 | |||
45 | if let (Some(ast_module), Some(name)) = (ast_name_parent, ast_name) { | ||
46 | return Some((name, ast_module)); | ||
47 | } | ||
48 | None | ||
49 | } | ||
50 | |||
51 | fn rename_mod( | ||
52 | db: &RootDatabase, | ||
53 | ast_name: &ast::Name, | ||
54 | ast_module: &ast::Module, | ||
55 | position: FilePosition, | ||
56 | new_name: &str, | ||
57 | ) -> Option<SourceChange> { | ||
58 | let mut source_file_edits = Vec::new(); | ||
59 | let mut file_system_edits = Vec::new(); | ||
60 | if let Some(module) = module_from_declaration(db, position.file_id, &ast_module) { | ||
61 | let (file_id, module_source) = module.definition_source(db); | ||
62 | match module_source { | ||
63 | ModuleSource::SourceFile(..) => { | ||
64 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | ||
65 | // mod is defined in path/to/dir/mod.rs | ||
66 | let dst_path = if mod_path.file_stem() == Some("mod") { | ||
67 | mod_path | ||
68 | .parent() | ||
69 | .and_then(|p| p.parent()) | ||
70 | .or_else(|| Some(RelativePath::new(""))) | ||
71 | .map(|p| p.join(new_name).join("mod.rs")) | ||
72 | } else { | ||
73 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | ||
74 | }; | ||
75 | if let Some(path) = dst_path { | ||
76 | let move_file = FileSystemEdit::MoveFile { | ||
77 | src: file_id, | ||
78 | dst_source_root: db.file_source_root(position.file_id), | ||
79 | dst_path: path, | ||
80 | }; | ||
81 | file_system_edits.push(move_file); | ||
82 | } | ||
83 | } | ||
84 | ModuleSource::Module(..) => {} | ||
85 | } | ||
86 | } | ||
87 | |||
88 | let edit = SourceFileEdit { | ||
89 | file_id: position.file_id, | ||
90 | edit: { | ||
91 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
92 | builder.replace(ast_name.syntax().range(), new_name.into()); | ||
93 | builder.finish() | ||
94 | }, | ||
95 | }; | ||
96 | source_file_edits.push(edit); | ||
97 | |||
98 | Some(SourceChange { | ||
99 | label: "rename".to_string(), | ||
100 | source_file_edits, | ||
101 | file_system_edits, | ||
102 | cursor_position: None, | ||
103 | }) | ||
104 | } | ||
105 | |||
106 | fn rename_reference( | ||
107 | db: &RootDatabase, | ||
108 | position: FilePosition, | ||
109 | new_name: &str, | ||
110 | ) -> Option<SourceChange> { | ||
111 | let edit = db | ||
112 | .find_all_refs(position) | ||
113 | .iter() | ||
114 | .map(|(file_id, text_range)| SourceFileEdit { | ||
115 | file_id: *file_id, | ||
116 | edit: { | ||
117 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
118 | builder.replace(*text_range, new_name.into()); | ||
119 | builder.finish() | ||
120 | }, | ||
121 | }) | ||
122 | .collect::<Vec<_>>(); | ||
123 | if edit.is_empty() { | ||
124 | return None; | ||
125 | } | ||
126 | |||
127 | Some(SourceChange { | ||
128 | label: "rename".to_string(), | ||
129 | source_file_edits: edit, | ||
130 | file_system_edits: Vec::new(), | ||
131 | cursor_position: None, | ||
132 | }) | ||
133 | } | ||
134 | |||
135 | #[cfg(test)] | ||
136 | mod tests { | ||
137 | use insta::assert_debug_snapshot_matches; | ||
138 | use test_utils::assert_eq_text; | ||
139 | use crate::{ | ||
140 | mock_analysis::single_file_with_position, | ||
141 | mock_analysis::analysis_and_position, | ||
142 | FileId | ||
143 | }; | ||
144 | |||
145 | #[test] | ||
146 | fn test_rename_for_local() { | ||
147 | test_rename( | ||
148 | r#" | ||
149 | fn main() { | ||
150 | let mut i = 1; | ||
151 | let j = 1; | ||
152 | i = i<|> + j; | ||
153 | |||
154 | { | ||
155 | i = 0; | ||
156 | } | ||
157 | |||
158 | i = 5; | ||
159 | }"#, | ||
160 | "k", | ||
161 | r#" | ||
162 | fn main() { | ||
163 | let mut k = 1; | ||
164 | let j = 1; | ||
165 | k = k + j; | ||
166 | |||
167 | { | ||
168 | k = 0; | ||
169 | } | ||
170 | |||
171 | k = 5; | ||
172 | }"#, | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | #[test] | ||
177 | fn test_rename_for_param_inside() { | ||
178 | test_rename( | ||
179 | r#" | ||
180 | fn foo(i : u32) -> u32 { | ||
181 | i<|> | ||
182 | }"#, | ||
183 | "j", | ||
184 | r#" | ||
185 | fn foo(j : u32) -> u32 { | ||
186 | j | ||
187 | }"#, | ||
188 | ); | ||
189 | } | ||
190 | |||
191 | #[test] | ||
192 | fn test_rename_refs_for_fn_param() { | ||
193 | test_rename( | ||
194 | r#" | ||
195 | fn foo(i<|> : u32) -> u32 { | ||
196 | i | ||
197 | }"#, | ||
198 | "new_name", | ||
199 | r#" | ||
200 | fn foo(new_name : u32) -> u32 { | ||
201 | new_name | ||
202 | }"#, | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_rename_for_mut_param() { | ||
208 | test_rename( | ||
209 | r#" | ||
210 | fn foo(mut i<|> : u32) -> u32 { | ||
211 | i | ||
212 | }"#, | ||
213 | "new_name", | ||
214 | r#" | ||
215 | fn foo(mut new_name : u32) -> u32 { | ||
216 | new_name | ||
217 | }"#, | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_rename_mod() { | ||
223 | let (analysis, position) = analysis_and_position( | ||
224 | " | ||
225 | //- /lib.rs | ||
226 | mod bar; | ||
227 | |||
228 | //- /bar.rs | ||
229 | mod foo<|>; | ||
230 | |||
231 | //- /bar/foo.rs | ||
232 | // emtpy | ||
233 | ", | ||
234 | ); | ||
235 | let new_name = "foo2"; | ||
236 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
237 | assert_debug_snapshot_matches!("rename_mod", &source_change); | ||
238 | } | ||
239 | |||
240 | #[test] | ||
241 | fn test_rename_mod_in_dir() { | ||
242 | let (analysis, position) = analysis_and_position( | ||
243 | " | ||
244 | //- /lib.rs | ||
245 | mod fo<|>o; | ||
246 | //- /foo/mod.rs | ||
247 | // emtpy | ||
248 | ", | ||
249 | ); | ||
250 | let new_name = "foo2"; | ||
251 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
252 | assert_debug_snapshot_matches!("rename_mod_in_dir", &source_change); | ||
253 | } | ||
254 | |||
255 | fn test_rename(text: &str, new_name: &str, expected: &str) { | ||
256 | let (analysis, position) = single_file_with_position(text); | ||
257 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
258 | let mut text_edit_bulder = ra_text_edit::TextEditBuilder::default(); | ||
259 | let mut file_id: Option<FileId> = None; | ||
260 | if let Some(change) = source_change { | ||
261 | for edit in change.source_file_edits { | ||
262 | file_id = Some(edit.file_id); | ||
263 | for atom in edit.edit.as_atoms() { | ||
264 | text_edit_bulder.replace(atom.delete, atom.insert.clone()); | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | let result = text_edit_bulder | ||
269 | .finish() | ||
270 | .apply(&*analysis.file_text(file_id.unwrap())); | ||
271 | assert_eq_text!(expected, &*result); | ||
272 | } | ||
273 | } | ||