diff options
author | Aleksey Kladov <[email protected]> | 2019-11-27 18:32:33 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-11-27 18:35:06 +0000 |
commit | 757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 (patch) | |
tree | d972d3a7e6457efdb5e0c558a8350db1818d07ae /crates/ra_ide/src/references/rename.rs | |
parent | d9a36a736bfb91578a36505e7237212959bb55fe (diff) |
rename ra_ide_api -> ra_ide
Diffstat (limited to 'crates/ra_ide/src/references/rename.rs')
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs new file mode 100644 index 000000000..d58496049 --- /dev/null +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -0,0 +1,328 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::ModuleSource; | ||
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabase, SourceDatabaseExt}; | ||
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; | ||
6 | use ra_text_edit::TextEdit; | ||
7 | |||
8 | use crate::{ | ||
9 | db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, | ||
10 | SourceFileEdit, TextRange, | ||
11 | }; | ||
12 | |||
13 | use super::find_all_refs; | ||
14 | |||
15 | pub(crate) fn rename( | ||
16 | db: &RootDatabase, | ||
17 | position: FilePosition, | ||
18 | new_name: &str, | ||
19 | ) -> Option<RangeInfo<SourceChange>> { | ||
20 | let parse = db.parse(position.file_id); | ||
21 | if let Some((ast_name, ast_module)) = | ||
22 | find_name_and_module_at_offset(parse.tree().syntax(), position) | ||
23 | { | ||
24 | let range = ast_name.syntax().text_range(); | ||
25 | rename_mod(db, &ast_name, &ast_module, position, new_name) | ||
26 | .map(|info| RangeInfo::new(range, info)) | ||
27 | } else { | ||
28 | rename_reference(db, position, new_name) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | fn find_name_and_module_at_offset( | ||
33 | syntax: &SyntaxNode, | ||
34 | position: FilePosition, | ||
35 | ) -> Option<(ast::Name, ast::Module)> { | ||
36 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; | ||
37 | let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; | ||
38 | Some((ast_name, ast_module)) | ||
39 | } | ||
40 | |||
41 | fn source_edit_from_file_id_range( | ||
42 | file_id: FileId, | ||
43 | range: TextRange, | ||
44 | new_name: &str, | ||
45 | ) -> SourceFileEdit { | ||
46 | SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } | ||
47 | } | ||
48 | |||
49 | fn rename_mod( | ||
50 | db: &RootDatabase, | ||
51 | ast_name: &ast::Name, | ||
52 | ast_module: &ast::Module, | ||
53 | position: FilePosition, | ||
54 | new_name: &str, | ||
55 | ) -> Option<SourceChange> { | ||
56 | let mut source_file_edits = Vec::new(); | ||
57 | let mut file_system_edits = Vec::new(); | ||
58 | let module_src = hir::Source { file_id: position.file_id.into(), value: ast_module.clone() }; | ||
59 | if let Some(module) = hir::Module::from_declaration(db, module_src) { | ||
60 | let src = module.definition_source(db); | ||
61 | let file_id = src.file_id.original_file(db); | ||
62 | match src.value { | ||
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: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), | ||
91 | }; | ||
92 | source_file_edits.push(edit); | ||
93 | |||
94 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | ||
95 | } | ||
96 | |||
97 | fn rename_reference( | ||
98 | db: &RootDatabase, | ||
99 | position: FilePosition, | ||
100 | new_name: &str, | ||
101 | ) -> Option<RangeInfo<SourceChange>> { | ||
102 | let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; | ||
103 | |||
104 | let edit = refs | ||
105 | .into_iter() | ||
106 | .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) | ||
107 | .collect::<Vec<_>>(); | ||
108 | |||
109 | if edit.is_empty() { | ||
110 | return None; | ||
111 | } | ||
112 | |||
113 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | ||
114 | } | ||
115 | |||
116 | #[cfg(test)] | ||
117 | mod tests { | ||
118 | use insta::assert_debug_snapshot; | ||
119 | use ra_text_edit::TextEditBuilder; | ||
120 | use test_utils::assert_eq_text; | ||
121 | |||
122 | use crate::{ | ||
123 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | ||
124 | }; | ||
125 | |||
126 | #[test] | ||
127 | fn test_rename_for_local() { | ||
128 | test_rename( | ||
129 | r#" | ||
130 | fn main() { | ||
131 | let mut i = 1; | ||
132 | let j = 1; | ||
133 | i = i<|> + j; | ||
134 | |||
135 | { | ||
136 | i = 0; | ||
137 | } | ||
138 | |||
139 | i = 5; | ||
140 | }"#, | ||
141 | "k", | ||
142 | r#" | ||
143 | fn main() { | ||
144 | let mut k = 1; | ||
145 | let j = 1; | ||
146 | k = k + j; | ||
147 | |||
148 | { | ||
149 | k = 0; | ||
150 | } | ||
151 | |||
152 | k = 5; | ||
153 | }"#, | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn test_rename_for_param_inside() { | ||
159 | test_rename( | ||
160 | r#" | ||
161 | fn foo(i : u32) -> u32 { | ||
162 | i<|> | ||
163 | }"#, | ||
164 | "j", | ||
165 | r#" | ||
166 | fn foo(j : u32) -> u32 { | ||
167 | j | ||
168 | }"#, | ||
169 | ); | ||
170 | } | ||
171 | |||
172 | #[test] | ||
173 | fn test_rename_refs_for_fn_param() { | ||
174 | test_rename( | ||
175 | r#" | ||
176 | fn foo(i<|> : u32) -> u32 { | ||
177 | i | ||
178 | }"#, | ||
179 | "new_name", | ||
180 | r#" | ||
181 | fn foo(new_name : u32) -> u32 { | ||
182 | new_name | ||
183 | }"#, | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_rename_for_mut_param() { | ||
189 | test_rename( | ||
190 | r#" | ||
191 | fn foo(mut i<|> : u32) -> u32 { | ||
192 | i | ||
193 | }"#, | ||
194 | "new_name", | ||
195 | r#" | ||
196 | fn foo(mut new_name : u32) -> u32 { | ||
197 | new_name | ||
198 | }"#, | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn test_rename_mod() { | ||
204 | let (analysis, position) = analysis_and_position( | ||
205 | " | ||
206 | //- /lib.rs | ||
207 | mod bar; | ||
208 | |||
209 | //- /bar.rs | ||
210 | mod foo<|>; | ||
211 | |||
212 | //- /bar/foo.rs | ||
213 | // emtpy | ||
214 | ", | ||
215 | ); | ||
216 | let new_name = "foo2"; | ||
217 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
218 | assert_debug_snapshot!(&source_change, | ||
219 | @r###" | ||
220 | Some( | ||
221 | RangeInfo { | ||
222 | range: [4; 7), | ||
223 | info: SourceChange { | ||
224 | label: "rename", | ||
225 | source_file_edits: [ | ||
226 | SourceFileEdit { | ||
227 | file_id: FileId( | ||
228 | 2, | ||
229 | ), | ||
230 | edit: TextEdit { | ||
231 | atoms: [ | ||
232 | AtomTextEdit { | ||
233 | delete: [4; 7), | ||
234 | insert: "foo2", | ||
235 | }, | ||
236 | ], | ||
237 | }, | ||
238 | }, | ||
239 | ], | ||
240 | file_system_edits: [ | ||
241 | MoveFile { | ||
242 | src: FileId( | ||
243 | 3, | ||
244 | ), | ||
245 | dst_source_root: SourceRootId( | ||
246 | 0, | ||
247 | ), | ||
248 | dst_path: "bar/foo2.rs", | ||
249 | }, | ||
250 | ], | ||
251 | cursor_position: None, | ||
252 | }, | ||
253 | }, | ||
254 | ) | ||
255 | "###); | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn test_rename_mod_in_dir() { | ||
260 | let (analysis, position) = analysis_and_position( | ||
261 | " | ||
262 | //- /lib.rs | ||
263 | mod fo<|>o; | ||
264 | //- /foo/mod.rs | ||
265 | // emtpy | ||
266 | ", | ||
267 | ); | ||
268 | let new_name = "foo2"; | ||
269 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
270 | assert_debug_snapshot!(&source_change, | ||
271 | @r###" | ||
272 | Some( | ||
273 | RangeInfo { | ||
274 | range: [4; 7), | ||
275 | info: SourceChange { | ||
276 | label: "rename", | ||
277 | source_file_edits: [ | ||
278 | SourceFileEdit { | ||
279 | file_id: FileId( | ||
280 | 1, | ||
281 | ), | ||
282 | edit: TextEdit { | ||
283 | atoms: [ | ||
284 | AtomTextEdit { | ||
285 | delete: [4; 7), | ||
286 | insert: "foo2", | ||
287 | }, | ||
288 | ], | ||
289 | }, | ||
290 | }, | ||
291 | ], | ||
292 | file_system_edits: [ | ||
293 | MoveFile { | ||
294 | src: FileId( | ||
295 | 2, | ||
296 | ), | ||
297 | dst_source_root: SourceRootId( | ||
298 | 0, | ||
299 | ), | ||
300 | dst_path: "foo2/mod.rs", | ||
301 | }, | ||
302 | ], | ||
303 | cursor_position: None, | ||
304 | }, | ||
305 | }, | ||
306 | ) | ||
307 | "### | ||
308 | ); | ||
309 | } | ||
310 | |||
311 | fn test_rename(text: &str, new_name: &str, expected: &str) { | ||
312 | let (analysis, position) = single_file_with_position(text); | ||
313 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
314 | let mut text_edit_builder = TextEditBuilder::default(); | ||
315 | let mut file_id: Option<FileId> = None; | ||
316 | if let Some(change) = source_change { | ||
317 | for edit in change.info.source_file_edits { | ||
318 | file_id = Some(edit.file_id); | ||
319 | for atom in edit.edit.as_atoms() { | ||
320 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | ||
321 | } | ||
322 | } | ||
323 | } | ||
324 | let result = | ||
325 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | ||
326 | assert_eq_text!(expected, &*result); | ||
327 | } | ||
328 | } | ||