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