aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/references.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/references.rs')
-rw-r--r--crates/ra_ide_api/src/references.rs417
1 files changed, 66 insertions, 351 deletions
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs
index e640b92cf..f54542787 100644
--- a/crates/ra_ide_api/src/references.rs
+++ b/crates/ra_ide_api/src/references.rs
@@ -1,16 +1,19 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::ModuleSource; 3mod classify;
4mod definition;
5mod rename;
6mod search_scope;
7
4use ra_db::{SourceDatabase, SourceDatabaseExt}; 8use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; 9use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit};
6use relative_path::{RelativePath, RelativePathBuf}; 10
7 11use crate::{db::RootDatabase, FileId, FilePosition, FileRange, NavigationTarget, RangeInfo};
8use crate::{ 12
9 db::RootDatabase, 13pub(crate) use self::{
10 name_kind::{classify_name, classify_name_ref, Definition, NameKind::*}, 14 classify::{classify_name, classify_name_ref},
11 search_scope::find_refs, 15 definition::{Definition, NameKind},
12 FileId, FilePosition, FileRange, FileSystemEdit, NavigationTarget, RangeInfo, SourceChange, 16 rename::rename,
13 SourceFileEdit, TextRange,
14}; 17};
15 18
16#[derive(Debug, Clone)] 19#[derive(Debug, Clone)]
@@ -59,169 +62,84 @@ pub(crate) fn find_all_refs(
59 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; 62 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
60 63
61 let declaration = match def.item { 64 let declaration = match def.item {
62 Macro(mac) => NavigationTarget::from_macro_def(db, mac), 65 NameKind::Macro(mac) => NavigationTarget::from_macro_def(db, mac),
63 FieldAccess(field) => NavigationTarget::from_field(db, field), 66 NameKind::FieldAccess(field) => NavigationTarget::from_field(db, field),
64 AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc), 67 NameKind::AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc),
65 Def(def) => NavigationTarget::from_def(db, def)?, 68 NameKind::Def(def) => NavigationTarget::from_def(db, def)?,
66 SelfType(ref ty) => match ty.as_adt() { 69 NameKind::SelfType(ref ty) => match ty.as_adt() {
67 Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id), 70 Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id),
68 None => return None, 71 None => return None,
69 }, 72 },
70 Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat), 73 NameKind::Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat),
71 SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par), 74 NameKind::SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par),
72 GenericParam(_) => return None, 75 NameKind::GenericParam(_) => return None,
73 }; 76 };
74 77
75 // let references = match name_kind { 78 let references = process_definition(db, def, name);
76 // Pat((_, pat)) => analyzer
77 // .find_all_refs(&pat.to_node(&syntax))
78 // .into_iter()
79 // .map(move |ref_desc| FileRange { file_id: position.file_id, range: ref_desc.range })
80 // .collect::<Vec<_>>(),
81 // _ => vec![],
82 // };
83 let references = find_refs(db, def, name);
84
85 return Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }));
86
87 fn find_name<'a>(
88 db: &RootDatabase,
89 syntax: &SyntaxNode,
90 position: FilePosition,
91 ) -> Option<RangeInfo<(String, Definition)>> {
92 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
93 let def = classify_name(db, position.file_id, &name)?;
94 let range = name.syntax().text_range();
95 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
96 }
97 let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?;
98 let range = name_ref.syntax().text_range();
99 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None);
100 let def = classify_name_ref(db, position.file_id, &analyzer, &name_ref)?;
101 Some(RangeInfo::new(range, (name_ref.text().to_string(), def)))
102 }
103}
104 79
105pub(crate) fn rename( 80 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
106 db: &RootDatabase,
107 position: FilePosition,
108 new_name: &str,
109) -> Option<RangeInfo<SourceChange>> {
110 let parse = db.parse(position.file_id);
111 if let Some((ast_name, ast_module)) =
112 find_name_and_module_at_offset(parse.tree().syntax(), position)
113 {
114 let range = ast_name.syntax().text_range();
115 rename_mod(db, &ast_name, &ast_module, position, new_name)
116 .map(|info| RangeInfo::new(range, info))
117 } else {
118 rename_reference(db, position, new_name)
119 }
120} 81}
121 82
122fn find_name_and_module_at_offset( 83fn find_name<'a>(
84 db: &RootDatabase,
123 syntax: &SyntaxNode, 85 syntax: &SyntaxNode,
124 position: FilePosition, 86 position: FilePosition,
125) -> Option<(ast::Name, ast::Module)> { 87) -> Option<RangeInfo<(String, Definition)>> {
126 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; 88 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
127 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; 89 let def = classify_name(db, position.file_id, &name)?;
128 Some((ast_name, ast_module)) 90 let range = name.syntax().text_range();
129} 91 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
130
131fn source_edit_from_file_id_range(
132 file_id: FileId,
133 range: TextRange,
134 new_name: &str,
135) -> SourceFileEdit {
136 SourceFileEdit {
137 file_id,
138 edit: {
139 let mut builder = ra_text_edit::TextEditBuilder::default();
140 builder.replace(range, new_name.into());
141 builder.finish()
142 },
143 } 92 }
93 let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?;
94 let def = classify_name_ref(db, position.file_id, &name_ref)?;
95 let range = name_ref.syntax().text_range();
96 Some(RangeInfo::new(range, (name_ref.text().to_string(), def)))
144} 97}
145 98
146fn rename_mod( 99fn process_definition(db: &RootDatabase, def: Definition, name: String) -> Vec<FileRange> {
147 db: &RootDatabase, 100 let pat = name.as_str();
148 ast_name: &ast::Name, 101 let scope = def.scope(db).scope;
149 ast_module: &ast::Module, 102 let mut refs = vec![];
150 position: FilePosition, 103
151 new_name: &str, 104 let is_match = |file_id: FileId, name_ref: &ast::NameRef| -> bool {
152) -> Option<SourceChange> { 105 let classified = classify_name_ref(db, file_id, &name_ref);
153 let mut source_file_edits = Vec::new(); 106 if let Some(d) = classified {
154 let mut file_system_edits = Vec::new(); 107 d == def
155 let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; 108 } else {
156 if let Some(module) = hir::Module::from_declaration(db, module_src) { 109 false
157 let src = module.definition_source(db);
158 let file_id = src.file_id.original_file(db);
159 match src.ast {
160 ModuleSource::SourceFile(..) => {
161 let mod_path: RelativePathBuf = db.file_relative_path(file_id);
162 // mod is defined in path/to/dir/mod.rs
163 let dst_path = if mod_path.file_stem() == Some("mod") {
164 mod_path
165 .parent()
166 .and_then(|p| p.parent())
167 .or_else(|| Some(RelativePath::new("")))
168 .map(|p| p.join(new_name).join("mod.rs"))
169 } else {
170 Some(mod_path.with_file_name(new_name).with_extension("rs"))
171 };
172 if let Some(path) = dst_path {
173 let move_file = FileSystemEdit::MoveFile {
174 src: file_id,
175 dst_source_root: db.file_source_root(position.file_id),
176 dst_path: path,
177 };
178 file_system_edits.push(move_file);
179 }
180 }
181 ModuleSource::Module(..) => {}
182 } 110 }
183 }
184
185 let edit = SourceFileEdit {
186 file_id: position.file_id,
187 edit: {
188 let mut builder = ra_text_edit::TextEditBuilder::default();
189 builder.replace(ast_name.syntax().text_range(), new_name.into());
190 builder.finish()
191 },
192 }; 111 };
193 source_file_edits.push(edit);
194
195 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
196}
197
198fn rename_reference(
199 db: &RootDatabase,
200 position: FilePosition,
201 new_name: &str,
202) -> Option<RangeInfo<SourceChange>> {
203 let RangeInfo { range, info: refs } = find_all_refs(db, position)?;
204
205 let edit = refs
206 .into_iter()
207 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name))
208 .collect::<Vec<_>>();
209 112
210 if edit.is_empty() { 113 for (file_id, text_range) in scope {
211 return None; 114 let text = db.file_text(file_id);
115 let parse = SourceFile::parse(&text);
116 let syntax = parse.tree().syntax().clone();
117
118 for (idx, _) in text.match_indices(pat) {
119 let offset = TextUnit::from_usize(idx);
120 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&syntax, offset) {
121 let range = name_ref.syntax().text_range();
122
123 if let Some(text_range) = text_range {
124 if range.is_subrange(&text_range) && is_match(file_id, &name_ref) {
125 refs.push(FileRange { file_id, range });
126 }
127 } else if is_match(file_id, &name_ref) {
128 refs.push(FileRange { file_id, range });
129 }
130 }
131 }
212 } 132 }
213 133
214 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) 134 return refs;
215} 135}
216 136
217#[cfg(test)] 137#[cfg(test)]
218mod tests { 138mod tests {
219 use crate::{ 139 use crate::{
220 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 140 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position,
221 ReferenceSearchResult, 141 ReferenceSearchResult,
222 }; 142 };
223 use insta::assert_debug_snapshot;
224 use test_utils::assert_eq_text;
225 143
226 #[test] 144 #[test]
227 fn test_find_all_refs_for_local() { 145 fn test_find_all_refs_for_local() {
@@ -353,207 +271,4 @@ mod tests {
353 let (analysis, position) = single_file_with_position(text); 271 let (analysis, position) = single_file_with_position(text);
354 analysis.find_all_refs(position).unwrap().unwrap() 272 analysis.find_all_refs(position).unwrap().unwrap()
355 } 273 }
356
357 #[test]
358 fn test_rename_for_local() {
359 test_rename(
360 r#"
361 fn main() {
362 let mut i = 1;
363 let j = 1;
364 i = i<|> + j;
365
366 {
367 i = 0;
368 }
369
370 i = 5;
371 }"#,
372 "k",
373 r#"
374 fn main() {
375 let mut k = 1;
376 let j = 1;
377 k = k + j;
378
379 {
380 k = 0;
381 }
382
383 k = 5;
384 }"#,
385 );
386 }
387
388 #[test]
389 fn test_rename_for_param_inside() {
390 test_rename(
391 r#"
392 fn foo(i : u32) -> u32 {
393 i<|>
394 }"#,
395 "j",
396 r#"
397 fn foo(j : u32) -> u32 {
398 j
399 }"#,
400 );
401 }
402
403 #[test]
404 fn test_rename_refs_for_fn_param() {
405 test_rename(
406 r#"
407 fn foo(i<|> : u32) -> u32 {
408 i
409 }"#,
410 "new_name",
411 r#"
412 fn foo(new_name : u32) -> u32 {
413 new_name
414 }"#,
415 );
416 }
417
418 #[test]
419 fn test_rename_for_mut_param() {
420 test_rename(
421 r#"
422 fn foo(mut i<|> : u32) -> u32 {
423 i
424 }"#,
425 "new_name",
426 r#"
427 fn foo(mut new_name : u32) -> u32 {
428 new_name
429 }"#,
430 );
431 }
432
433 #[test]
434 fn test_rename_mod() {
435 let (analysis, position) = analysis_and_position(
436 "
437 //- /lib.rs
438 mod bar;
439
440 //- /bar.rs
441 mod foo<|>;
442
443 //- /bar/foo.rs
444 // emtpy
445 ",
446 );
447 let new_name = "foo2";
448 let source_change = analysis.rename(position, new_name).unwrap();
449 assert_debug_snapshot!(&source_change,
450@r###"
451 Some(
452 RangeInfo {
453 range: [4; 7),
454 info: SourceChange {
455 label: "rename",
456 source_file_edits: [
457 SourceFileEdit {
458 file_id: FileId(
459 2,
460 ),
461 edit: TextEdit {
462 atoms: [
463 AtomTextEdit {
464 delete: [4; 7),
465 insert: "foo2",
466 },
467 ],
468 },
469 },
470 ],
471 file_system_edits: [
472 MoveFile {
473 src: FileId(
474 3,
475 ),
476 dst_source_root: SourceRootId(
477 0,
478 ),
479 dst_path: "bar/foo2.rs",
480 },
481 ],
482 cursor_position: None,
483 },
484 },
485 )
486 "###);
487 }
488
489 #[test]
490 fn test_rename_mod_in_dir() {
491 let (analysis, position) = analysis_and_position(
492 "
493 //- /lib.rs
494 mod fo<|>o;
495 //- /foo/mod.rs
496 // emtpy
497 ",
498 );
499 let new_name = "foo2";
500 let source_change = analysis.rename(position, new_name).unwrap();
501 assert_debug_snapshot!(&source_change,
502 @r###"
503 Some(
504 RangeInfo {
505 range: [4; 7),
506 info: SourceChange {
507 label: "rename",
508 source_file_edits: [
509 SourceFileEdit {
510 file_id: FileId(
511 1,
512 ),
513 edit: TextEdit {
514 atoms: [
515 AtomTextEdit {
516 delete: [4; 7),
517 insert: "foo2",
518 },
519 ],
520 },
521 },
522 ],
523 file_system_edits: [
524 MoveFile {
525 src: FileId(
526 2,
527 ),
528 dst_source_root: SourceRootId(
529 0,
530 ),
531 dst_path: "foo2/mod.rs",
532 },
533 ],
534 cursor_position: None,
535 },
536 },
537 )
538 "###
539 );
540 }
541
542 fn test_rename(text: &str, new_name: &str, expected: &str) {
543 let (analysis, position) = single_file_with_position(text);
544 let source_change = analysis.rename(position, new_name).unwrap();
545 let mut text_edit_builder = ra_text_edit::TextEditBuilder::default();
546 let mut file_id: Option<FileId> = None;
547 if let Some(change) = source_change {
548 for edit in change.info.source_file_edits {
549 file_id = Some(edit.file_id);
550 for atom in edit.edit.as_atoms() {
551 text_edit_builder.replace(atom.delete, atom.insert.clone());
552 }
553 }
554 }
555 let result =
556 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
557 assert_eq_text!(expected, &*result);
558 }
559} 274}