aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/from_source.rs1
-rw-r--r--crates/ra_ide_api/Cargo.toml1
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs12
-rw-r--r--crates/ra_ide_api/src/hover.rs11
-rw-r--r--crates/ra_ide_api/src/lib.rs1
-rw-r--r--crates/ra_ide_api/src/name_ref_kind.rs98
-rw-r--r--crates/ra_ide_api/src/references.rs520
-rw-r--r--crates/ra_ide_api/src/references/classify.rs179
-rw-r--r--crates/ra_ide_api/src/references/name_definition.rs113
-rw-r--r--crates/ra_ide_api/src/references/rename.rs469
-rw-r--r--crates/ra_ide_api/src/references/search_scope.rs92
-rw-r--r--crates/ra_ide_api/src/syntax_highlighting.rs14
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs12
13 files changed, 1074 insertions, 449 deletions
diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs
index f80d8eb5f..a137aeb90 100644
--- a/crates/ra_hir/src/from_source.rs
+++ b/crates/ra_hir/src/from_source.rs
@@ -181,7 +181,6 @@ impl Module {
181 ) -> Option<Self> { 181 ) -> Option<Self> {
182 let decl_id = match src.ast { 182 let decl_id = match src.ast {
183 ModuleSource::Module(ref module) => { 183 ModuleSource::Module(ref module) => {
184 assert!(!module.has_semi());
185 let ast_id_map = db.ast_id_map(src.file_id); 184 let ast_id_map = db.ast_id_map(src.file_id);
186 let item_id = ast_id_map.ast_id(module).with_file_id(src.file_id); 185 let item_id = ast_id_map.ast_id(module).with_file_id(src.file_id);
187 Some(item_id) 186 Some(item_id)
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
index 73f39b647..f9bf0c686 100644
--- a/crates/ra_ide_api/Cargo.toml
+++ b/crates/ra_ide_api/Cargo.toml
@@ -19,6 +19,7 @@ rustc-hash = "1.0"
19unicase = "2.2.0" 19unicase = "2.2.0"
20superslice = "1.0.0" 20superslice = "1.0.0"
21rand = { version = "0.7.0", features = ["small_rng"] } 21rand = { version = "0.7.0", features = ["small_rng"] }
22once_cell = "1.2.0"
22 23
23ra_syntax = { path = "../ra_syntax" } 24ra_syntax = { path = "../ra_syntax" }
24ra_text_edit = { path = "../ra_text_edit" } 25ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs
index 41a88314f..1f3fa6c57 100644
--- a/crates/ra_ide_api/src/goto_definition.rs
+++ b/crates/ra_ide_api/src/goto_definition.rs
@@ -10,7 +10,7 @@ use ra_syntax::{
10use crate::{ 10use crate::{
11 db::RootDatabase, 11 db::RootDatabase,
12 display::ShortLabel, 12 display::ShortLabel,
13 name_ref_kind::{classify_name_ref, NameRefKind::*}, 13 references::{classify_name_ref, NameKind::*},
14 FilePosition, NavigationTarget, RangeInfo, 14 FilePosition, NavigationTarget, RangeInfo,
15}; 15};
16 16
@@ -54,13 +54,11 @@ pub(crate) fn reference_definition(
54) -> ReferenceResult { 54) -> ReferenceResult {
55 use self::ReferenceResult::*; 55 use self::ReferenceResult::*;
56 56
57 let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); 57 let name_kind = classify_name_ref(db, file_id, &name_ref).map(|d| d.kind);
58 58 match name_kind {
59 match classify_name_ref(db, &analyzer, name_ref) {
60 Some(Macro(mac)) => return Exact(NavigationTarget::from_macro_def(db, mac)), 59 Some(Macro(mac)) => return Exact(NavigationTarget::from_macro_def(db, mac)),
61 Some(FieldAccess(field)) => return Exact(NavigationTarget::from_field(db, field)), 60 Some(Field(field)) => return Exact(NavigationTarget::from_field(db, field)),
62 Some(AssocItem(assoc)) => return Exact(NavigationTarget::from_assoc_item(db, assoc)), 61 Some(AssocItem(assoc)) => return Exact(NavigationTarget::from_assoc_item(db, assoc)),
63 Some(Method(func)) => return Exact(NavigationTarget::from_def_source(db, func)),
64 Some(Def(def)) => match NavigationTarget::from_def(db, def) { 62 Some(Def(def)) => match NavigationTarget::from_def(db, def) {
65 Some(nav) => return Exact(nav), 63 Some(nav) => return Exact(nav),
66 None => return Approximate(vec![]), 64 None => return Approximate(vec![]),
@@ -70,7 +68,7 @@ pub(crate) fn reference_definition(
70 return Exact(NavigationTarget::from_adt_def(db, def_id)); 68 return Exact(NavigationTarget::from_adt_def(db, def_id));
71 } 69 }
72 } 70 }
73 Some(Pat(pat)) => return Exact(NavigationTarget::from_pat(db, file_id, pat)), 71 Some(Pat((_, pat))) => return Exact(NavigationTarget::from_pat(db, file_id, pat)),
74 Some(SelfParam(par)) => return Exact(NavigationTarget::from_self_param(file_id, par)), 72 Some(SelfParam(par)) => return Exact(NavigationTarget::from_self_param(file_id, par)),
75 Some(GenericParam(_)) => { 73 Some(GenericParam(_)) => {
76 // FIXME: go to the generic param def 74 // FIXME: go to the generic param def
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs
index 24b161c5c..ba328efa1 100644
--- a/crates/ra_ide_api/src/hover.rs
+++ b/crates/ra_ide_api/src/hover.rs
@@ -14,7 +14,7 @@ use crate::{
14 description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, 14 description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
15 rust_code_markup_with_doc, ShortLabel, 15 rust_code_markup_with_doc, ShortLabel,
16 }, 16 },
17 name_ref_kind::{classify_name_ref, NameRefKind::*}, 17 references::{classify_name_ref, NameKind::*},
18 FilePosition, FileRange, RangeInfo, 18 FilePosition, FileRange, RangeInfo,
19}; 19};
20 20
@@ -99,17 +99,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
99 99
100 let mut range = None; 100 let mut range = None;
101 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { 101 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
102 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None);
103
104 let mut no_fallback = false; 102 let mut no_fallback = false;
105 103 let name_kind = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind);
106 match classify_name_ref(db, &analyzer, &name_ref) { 104 match name_kind {
107 Some(Method(it)) => res.extend(from_def_source(db, it)),
108 Some(Macro(it)) => { 105 Some(Macro(it)) => {
109 let src = it.source(db); 106 let src = it.source(db);
110 res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast)))); 107 res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast))));
111 } 108 }
112 Some(FieldAccess(it)) => { 109 Some(Field(it)) => {
113 let src = it.source(db); 110 let src = it.source(db);
114 if let hir::FieldSource::Named(it) = src.ast { 111 if let hir::FieldSource::Named(it) = src.ast {
115 res.extend(hover_text(it.doc_comment_text(), it.short_label())); 112 res.extend(hover_text(it.doc_comment_text(), it.short_label()));
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index f7fd42f65..19669a7f0 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -19,7 +19,6 @@ mod feature_flags;
19mod status; 19mod status;
20mod completion; 20mod completion;
21mod runnables; 21mod runnables;
22mod name_ref_kind;
23mod goto_definition; 22mod goto_definition;
24mod goto_type_definition; 23mod goto_type_definition;
25mod extend_selection; 24mod extend_selection;
diff --git a/crates/ra_ide_api/src/name_ref_kind.rs b/crates/ra_ide_api/src/name_ref_kind.rs
deleted file mode 100644
index 149585971..000000000
--- a/crates/ra_ide_api/src/name_ref_kind.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1//! FIXME: write short doc here
2
3use hir::Either;
4use ra_syntax::{ast, AstNode, AstPtr};
5use test_utils::tested_by;
6
7use crate::db::RootDatabase;
8
9pub enum NameRefKind {
10 Method(hir::Function),
11 Macro(hir::MacroDef),
12 FieldAccess(hir::StructField),
13 AssocItem(hir::AssocItem),
14 Def(hir::ModuleDef),
15 SelfType(hir::Ty),
16 Pat(AstPtr<ast::BindPat>),
17 SelfParam(AstPtr<ast::SelfParam>),
18 GenericParam(u32),
19}
20
21pub(crate) fn classify_name_ref(
22 db: &RootDatabase,
23 analyzer: &hir::SourceAnalyzer,
24 name_ref: &ast::NameRef,
25) -> Option<NameRefKind> {
26 use NameRefKind::*;
27
28 // Check if it is a method
29 if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) {
30 tested_by!(goto_definition_works_for_methods);
31 if let Some(func) = analyzer.resolve_method_call(&method_call) {
32 return Some(Method(func));
33 }
34 }
35
36 // It could be a macro call
37 if let Some(macro_call) = name_ref
38 .syntax()
39 .parent()
40 .and_then(|node| node.parent())
41 .and_then(|node| node.parent())
42 .and_then(ast::MacroCall::cast)
43 {
44 tested_by!(goto_definition_works_for_macros);
45 if let Some(mac) = analyzer.resolve_macro_call(db, &macro_call) {
46 return Some(Macro(mac));
47 }
48 }
49
50 // It could also be a field access
51 if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
52 tested_by!(goto_definition_works_for_fields);
53 if let Some(field) = analyzer.resolve_field(&field_expr) {
54 return Some(FieldAccess(field));
55 };
56 }
57
58 // It could also be a named field
59 if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::RecordField::cast) {
60 tested_by!(goto_definition_works_for_record_fields);
61
62 let record_lit = field_expr.syntax().ancestors().find_map(ast::RecordLit::cast);
63
64 if let Some(ty) = record_lit.and_then(|lit| analyzer.type_of(db, &lit.into())) {
65 if let Some((hir::Adt::Struct(s), _)) = ty.as_adt() {
66 let hir_path = hir::Path::from_name_ref(name_ref);
67 let hir_name = hir_path.as_ident().unwrap();
68
69 if let Some(field) = s.field(db, hir_name) {
70 return Some(FieldAccess(field));
71 }
72 }
73 }
74 }
75
76 // General case, a path or a local:
77 if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
78 if let Some(resolved) = analyzer.resolve_path(db, &path) {
79 return match resolved {
80 hir::PathResolution::Def(def) => Some(Def(def)),
81 hir::PathResolution::LocalBinding(Either::A(pat)) => Some(Pat(pat)),
82 hir::PathResolution::LocalBinding(Either::B(par)) => Some(SelfParam(par)),
83 hir::PathResolution::GenericParam(par) => {
84 // FIXME: get generic param def
85 Some(GenericParam(par))
86 }
87 hir::PathResolution::Macro(def) => Some(Macro(def)),
88 hir::PathResolution::SelfType(impl_block) => {
89 let ty = impl_block.target_ty(db);
90 Some(SelfType(ty))
91 }
92 hir::PathResolution::AssocItem(assoc) => Some(AssocItem(assoc)),
93 };
94 }
95 }
96
97 None
98}
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs
index 4247c6d90..f35d835ac 100644
--- a/crates/ra_ide_api/src/references.rs
+++ b/crates/ra_ide_api/src/references.rs
@@ -1,13 +1,29 @@
1//! FIXME: write short doc here 1//! This module implements a reference search.
2 2//! First, the element at the cursor position must be either an `ast::Name`
3use hir::{Either, ModuleSource}; 3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching.
7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference.
11
12mod classify;
13mod name_definition;
14mod rename;
15mod search_scope;
16
17use once_cell::unsync::Lazy;
4use ra_db::{SourceDatabase, SourceDatabaseExt}; 18use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode}; 19use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit};
6use relative_path::{RelativePath, RelativePathBuf}; 20
21use crate::{db::RootDatabase, FilePosition, FileRange, NavigationTarget, RangeInfo};
7 22
8use crate::{ 23pub(crate) use self::{
9 db::RootDatabase, FileId, FilePosition, FileRange, FileSystemEdit, NavigationTarget, RangeInfo, 24 classify::{classify_name, classify_name_ref},
10 SourceChange, SourceFileEdit, TextRange, 25 name_definition::{NameDefinition, NameKind},
26 rename::rename,
11}; 27};
12 28
13#[derive(Debug, Clone)] 29#[derive(Debug, Clone)]
@@ -52,161 +68,82 @@ pub(crate) fn find_all_refs(
52 position: FilePosition, 68 position: FilePosition,
53) -> Option<RangeInfo<ReferenceSearchResult>> { 69) -> Option<RangeInfo<ReferenceSearchResult>> {
54 let parse = db.parse(position.file_id); 70 let parse = db.parse(position.file_id);
55 let RangeInfo { range, info: (binding, analyzer) } = find_binding(db, &parse.tree(), position)?; 71 let syntax = parse.tree().syntax().clone();
56 let declaration = NavigationTarget::from_bind_pat(position.file_id, &binding); 72 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
57 73
58 let references = analyzer 74 let declaration = match def.kind {
59 .find_all_refs(&binding) 75 NameKind::Macro(mac) => NavigationTarget::from_macro_def(db, mac),
60 .into_iter() 76 NameKind::Field(field) => NavigationTarget::from_field(db, field),
61 .map(move |ref_desc| FileRange { file_id: position.file_id, range: ref_desc.range }) 77 NameKind::AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc),
62 .collect::<Vec<_>>(); 78 NameKind::Def(def) => NavigationTarget::from_def(db, def)?,
63 79 NameKind::SelfType(ref ty) => match ty.as_adt() {
64 return Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })); 80 Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id),
65 81 None => return None,
66 fn find_binding<'a>( 82 },
67 db: &RootDatabase, 83 NameKind::Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat),
68 source_file: &SourceFile, 84 NameKind::SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par),
69 position: FilePosition, 85 NameKind::GenericParam(_) => return None,
70 ) -> Option<RangeInfo<(ast::BindPat, hir::SourceAnalyzer)>> { 86 };
71 let syntax = source_file.syntax();
72 if let Some(binding) = find_node_at_offset::<ast::BindPat>(syntax, position.offset) {
73 let range = binding.syntax().text_range();
74 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, binding.syntax(), None);
75 return Some(RangeInfo::new(range, (binding, analyzer)));
76 };
77 let name_ref = find_node_at_offset::<ast::NameRef>(syntax, position.offset)?;
78 let range = name_ref.syntax().text_range();
79 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None);
80 let resolved = analyzer.resolve_local_name(&name_ref)?;
81 if let Either::A(ptr) = resolved.ptr() {
82 if let ast::Pat::BindPat(binding) = ptr.to_node(source_file.syntax()) {
83 return Some(RangeInfo::new(range, (binding, analyzer)));
84 }
85 }
86 None
87 }
88}
89 87
90pub(crate) fn rename( 88 let references = process_definition(db, def, name);
91 db: &RootDatabase, 89
92 position: FilePosition, 90 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
93 new_name: &str,
94) -> Option<RangeInfo<SourceChange>> {
95 let parse = db.parse(position.file_id);
96 if let Some((ast_name, ast_module)) =
97 find_name_and_module_at_offset(parse.tree().syntax(), position)
98 {
99 let range = ast_name.syntax().text_range();
100 rename_mod(db, &ast_name, &ast_module, position, new_name)
101 .map(|info| RangeInfo::new(range, info))
102 } else {
103 rename_reference(db, position, new_name)
104 }
105} 91}
106 92
107fn find_name_and_module_at_offset( 93fn find_name<'a>(
94 db: &RootDatabase,
108 syntax: &SyntaxNode, 95 syntax: &SyntaxNode,
109 position: FilePosition, 96 position: FilePosition,
110) -> Option<(ast::Name, ast::Module)> { 97) -> Option<RangeInfo<(String, NameDefinition)>> {
111 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; 98 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
112 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; 99 let def = classify_name(db, position.file_id, &name)?;
113 Some((ast_name, ast_module)) 100 let range = name.syntax().text_range();
114} 101 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
115
116fn source_edit_from_file_id_range(
117 file_id: FileId,
118 range: TextRange,
119 new_name: &str,
120) -> SourceFileEdit {
121 SourceFileEdit {
122 file_id,
123 edit: {
124 let mut builder = ra_text_edit::TextEditBuilder::default();
125 builder.replace(range, new_name.into());
126 builder.finish()
127 },
128 } 102 }
103 let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?;
104 let def = classify_name_ref(db, position.file_id, &name_ref)?;
105 let range = name_ref.syntax().text_range();
106 Some(RangeInfo::new(range, (name_ref.text().to_string(), def)))
129} 107}
130 108
131fn rename_mod( 109fn process_definition(db: &RootDatabase, def: NameDefinition, name: String) -> Vec<FileRange> {
132 db: &RootDatabase, 110 let pat = name.as_str();
133 ast_name: &ast::Name, 111 let scope = def.search_scope(db);
134 ast_module: &ast::Module, 112 let mut refs = vec![];
135 position: FilePosition, 113
136 new_name: &str, 114 for (file_id, search_range) in scope {
137) -> Option<SourceChange> { 115 let text = db.file_text(file_id);
138 let mut source_file_edits = Vec::new(); 116 let parse = Lazy::new(|| SourceFile::parse(&text));
139 let mut file_system_edits = Vec::new(); 117
140 let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; 118 for (idx, _) in text.match_indices(pat) {
141 if let Some(module) = hir::Module::from_declaration(db, module_src) { 119 let offset = TextUnit::from_usize(idx);
142 let src = module.definition_source(db); 120
143 let file_id = src.file_id.original_file(db); 121 if let Some(name_ref) =
144 match src.ast { 122 find_node_at_offset::<ast::NameRef>(parse.tree().syntax(), offset)
145 ModuleSource::SourceFile(..) => { 123 {
146 let mod_path: RelativePathBuf = db.file_relative_path(file_id); 124 let range = name_ref.syntax().text_range();
147 // mod is defined in path/to/dir/mod.rs 125 if let Some(search_range) = search_range {
148 let dst_path = if mod_path.file_stem() == Some("mod") { 126 if !range.is_subrange(&search_range) {
149 mod_path 127 continue;
150 .parent() 128 }
151 .and_then(|p| p.parent()) 129 }
152 .or_else(|| Some(RelativePath::new(""))) 130 if let Some(d) = classify_name_ref(db, file_id, &name_ref) {
153 .map(|p| p.join(new_name).join("mod.rs")) 131 if d == def {
154 } else { 132 refs.push(FileRange { file_id, range });
155 Some(mod_path.with_file_name(new_name).with_extension("rs")) 133 }
156 };
157 if let Some(path) = dst_path {
158 let move_file = FileSystemEdit::MoveFile {
159 src: file_id,
160 dst_source_root: db.file_source_root(position.file_id),
161 dst_path: path,
162 };
163 file_system_edits.push(move_file);
164 } 134 }
165 } 135 }
166 ModuleSource::Module(..) => {}
167 } 136 }
168 } 137 }
169 138 refs
170 let edit = SourceFileEdit {
171 file_id: position.file_id,
172 edit: {
173 let mut builder = ra_text_edit::TextEditBuilder::default();
174 builder.replace(ast_name.syntax().text_range(), new_name.into());
175 builder.finish()
176 },
177 };
178 source_file_edits.push(edit);
179
180 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
181}
182
183fn rename_reference(
184 db: &RootDatabase,
185 position: FilePosition,
186 new_name: &str,
187) -> Option<RangeInfo<SourceChange>> {
188 let RangeInfo { range, info: refs } = find_all_refs(db, position)?;
189
190 let edit = refs
191 .into_iter()
192 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name))
193 .collect::<Vec<_>>();
194
195 if edit.is_empty() {
196 return None;
197 }
198
199 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit)))
200} 139}
201 140
202#[cfg(test)] 141#[cfg(test)]
203mod tests { 142mod tests {
204 use crate::{ 143 use crate::{
205 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 144 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position,
206 ReferenceSearchResult, 145 ReferenceSearchResult,
207 }; 146 };
208 use insta::assert_debug_snapshot;
209 use test_utils::assert_eq_text;
210 147
211 #[test] 148 #[test]
212 fn test_find_all_refs_for_local() { 149 fn test_find_all_refs_for_local() {
@@ -249,211 +186,144 @@ mod tests {
249 assert_eq!(refs.len(), 2); 186 assert_eq!(refs.len(), 2);
250 } 187 }
251 188
252 fn get_all_refs(text: &str) -> ReferenceSearchResult {
253 let (analysis, position) = single_file_with_position(text);
254 analysis.find_all_refs(position).unwrap().unwrap()
255 }
256
257 #[test] 189 #[test]
258 fn test_rename_for_local() { 190 fn test_find_all_refs_field_name() {
259 test_rename( 191 let code = r#"
260 r#" 192 //- /lib.rs
261 fn main() { 193 struct Foo {
262 let mut i = 1; 194 pub spam<|>: u32,
263 let j = 1; 195 }
264 i = i<|> + j;
265
266 {
267 i = 0;
268 }
269
270 i = 5;
271 }"#,
272 "k",
273 r#"
274 fn main() {
275 let mut k = 1;
276 let j = 1;
277 k = k + j;
278 196
279 { 197 fn main(s: Foo) {
280 k = 0; 198 let f = s.spam;
281 } 199 }
200 "#;
282 201
283 k = 5; 202 let refs = get_all_refs(code);
284 }"#, 203 assert_eq!(refs.len(), 2);
285 );
286 } 204 }
287 205
288 #[test] 206 #[test]
289 fn test_rename_for_param_inside() { 207 fn test_find_all_refs_impl_item_name() {
290 test_rename( 208 let code = r#"
291 r#" 209 //- /lib.rs
292 fn foo(i : u32) -> u32 { 210 struct Foo;
293 i<|> 211 impl Foo {
294 }"#, 212 fn f<|>(&self) { }
295 "j", 213 }
296 r#" 214 "#;
297 fn foo(j : u32) -> u32 {
298 j
299 }"#,
300 );
301 }
302 215
303 #[test] 216 let refs = get_all_refs(code);
304 fn test_rename_refs_for_fn_param() { 217 assert_eq!(refs.len(), 1);
305 test_rename(
306 r#"
307 fn foo(i<|> : u32) -> u32 {
308 i
309 }"#,
310 "new_name",
311 r#"
312 fn foo(new_name : u32) -> u32 {
313 new_name
314 }"#,
315 );
316 } 218 }
317 219
318 #[test] 220 #[test]
319 fn test_rename_for_mut_param() { 221 fn test_find_all_refs_enum_var_name() {
320 test_rename( 222 let code = r#"
321 r#" 223 //- /lib.rs
322 fn foo(mut i<|> : u32) -> u32 { 224 enum Foo {
323 i 225 A,
324 }"#, 226 B<|>,
325 "new_name", 227 C,
326 r#" 228 }
327 fn foo(mut new_name : u32) -> u32 { 229 "#;
328 new_name 230
329 }"#, 231 let refs = get_all_refs(code);
330 ); 232 assert_eq!(refs.len(), 1);
331 } 233 }
332 234
333 #[test] 235 #[test]
334 fn test_rename_mod() { 236 fn test_find_all_refs_two_modules() {
335 let (analysis, position) = analysis_and_position( 237 let code = r#"
336 "
337 //- /lib.rs 238 //- /lib.rs
338 mod bar; 239 pub mod foo;
240 pub mod bar;
241
242 fn f() {
243 let i = foo::Foo { n: 5 };
244 }
245
246 //- /foo.rs
247 use crate::bar;
248
249 pub struct Foo {
250 pub n: u32,
251 }
252
253 fn f() {
254 let i = bar::Bar { n: 5 };
255 }
339 256
340 //- /bar.rs 257 //- /bar.rs
258 use crate::foo;
259
260 pub struct Bar {
261 pub n: u32,
262 }
263
264 fn f() {
265 let i = foo::Foo<|> { n: 5 };
266 }
267 "#;
268
269 let (analysis, pos) = analysis_and_position(code);
270 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
271 assert_eq!(refs.len(), 3);
272 }
273
274 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
275 // So, there are two references: the first one is a definition of the `foo` module,
276 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
277 #[test]
278 fn test_find_all_refs_decl_module() {
279 let code = r#"
280 //- /lib.rs
341 mod foo<|>; 281 mod foo<|>;
342 282
343 //- /bar/foo.rs 283 use foo::Foo;
344 // emtpy 284
345 ", 285 fn f() {
346 ); 286 let i = Foo { n: 5 };
347 let new_name = "foo2"; 287 }
348 let source_change = analysis.rename(position, new_name).unwrap(); 288
349 assert_debug_snapshot!(&source_change, 289 //- /foo.rs
350@r###" 290 pub struct Foo {
351 Some( 291 pub n: u32,
352 RangeInfo { 292 }
353 range: [4; 7), 293 "#;
354 info: SourceChange { 294
355 label: "rename", 295 let (analysis, pos) = analysis_and_position(code);
356 source_file_edits: [ 296 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
357 SourceFileEdit { 297 assert_eq!(refs.len(), 2);
358 file_id: FileId(
359 2,
360 ),
361 edit: TextEdit {
362 atoms: [
363 AtomTextEdit {
364 delete: [4; 7),
365 insert: "foo2",
366 },
367 ],
368 },
369 },
370 ],
371 file_system_edits: [
372 MoveFile {
373 src: FileId(
374 3,
375 ),
376 dst_source_root: SourceRootId(
377 0,
378 ),
379 dst_path: "bar/foo2.rs",
380 },
381 ],
382 cursor_position: None,
383 },
384 },
385 )
386 "###);
387 } 298 }
388 299
389 #[test] 300 #[test]
390 fn test_rename_mod_in_dir() { 301 fn test_find_all_refs_super_mod_vis() {
391 let (analysis, position) = analysis_and_position( 302 let code = r#"
392 "
393 //- /lib.rs 303 //- /lib.rs
394 mod fo<|>o; 304 mod foo;
395 //- /foo/mod.rs 305
396 // emtpy 306 //- /foo.rs
397 ", 307 mod some;
398 ); 308 use some::Foo;
399 let new_name = "foo2"; 309
400 let source_change = analysis.rename(position, new_name).unwrap(); 310 fn f() {
401 assert_debug_snapshot!(&source_change, 311 let i = Foo { n: 5 };
402 @r###" 312 }
403 Some( 313
404 RangeInfo { 314 //- /foo/some.rs
405 range: [4; 7), 315 pub(super) struct Foo<|> {
406 info: SourceChange { 316 pub n: u32,
407 label: "rename", 317 }
408 source_file_edits: [ 318 "#;
409 SourceFileEdit { 319
410 file_id: FileId( 320 let (analysis, pos) = analysis_and_position(code);
411 1, 321 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
412 ), 322 assert_eq!(refs.len(), 3);
413 edit: TextEdit {
414 atoms: [
415 AtomTextEdit {
416 delete: [4; 7),
417 insert: "foo2",
418 },
419 ],
420 },
421 },
422 ],
423 file_system_edits: [
424 MoveFile {
425 src: FileId(
426 2,
427 ),
428 dst_source_root: SourceRootId(
429 0,
430 ),
431 dst_path: "foo2/mod.rs",
432 },
433 ],
434 cursor_position: None,
435 },
436 },
437 )
438 "###
439 );
440 } 323 }
441 324
442 fn test_rename(text: &str, new_name: &str, expected: &str) { 325 fn get_all_refs(text: &str) -> ReferenceSearchResult {
443 let (analysis, position) = single_file_with_position(text); 326 let (analysis, position) = single_file_with_position(text);
444 let source_change = analysis.rename(position, new_name).unwrap(); 327 analysis.find_all_refs(position).unwrap().unwrap()
445 let mut text_edit_builder = ra_text_edit::TextEditBuilder::default();
446 let mut file_id: Option<FileId> = None;
447 if let Some(change) = source_change {
448 for edit in change.info.source_file_edits {
449 file_id = Some(edit.file_id);
450 for atom in edit.edit.as_atoms() {
451 text_edit_builder.replace(atom.delete, atom.insert.clone());
452 }
453 }
454 }
455 let result =
456 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
457 assert_eq_text!(expected, &*result);
458 } 328 }
459} 329}
diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs
new file mode 100644
index 000000000..c8daff9b1
--- /dev/null
+++ b/crates/ra_ide_api/src/references/classify.rs
@@ -0,0 +1,179 @@
1//! Functions that are used to classify an element from its definition or reference.
2
3use hir::{Either, FromSource, Module, ModuleSource, Path, PathResolution, Source, SourceAnalyzer};
4use ra_db::FileId;
5use ra_syntax::{ast, match_ast, AstNode, AstPtr};
6use test_utils::tested_by;
7
8use super::{
9 name_definition::{from_assoc_item, from_module_def, from_pat, from_struct_field},
10 NameDefinition, NameKind,
11};
12use crate::db::RootDatabase;
13
14pub(crate) fn classify_name(
15 db: &RootDatabase,
16 file_id: FileId,
17 name: &ast::Name,
18) -> Option<NameDefinition> {
19 let parent = name.syntax().parent()?;
20 let file_id = file_id.into();
21
22 // FIXME: add ast::MacroCall(it)
23 match_ast! {
24 match parent {
25 ast::BindPat(it) => {
26 from_pat(db, file_id, AstPtr::new(&it))
27 },
28 ast::RecordFieldDef(it) => {
29 let ast = hir::FieldSource::Named(it);
30 let src = hir::Source { file_id, ast };
31 let field = hir::StructField::from_source(db, src)?;
32 Some(from_struct_field(db, field))
33 },
34 ast::Module(it) => {
35 let def = {
36 if !it.has_semi() {
37 let ast = hir::ModuleSource::Module(it);
38 let src = hir::Source { file_id, ast };
39 hir::Module::from_definition(db, src)
40 } else {
41 let src = hir::Source { file_id, ast: it };
42 hir::Module::from_declaration(db, src)
43 }
44 }?;
45 Some(from_module_def(db, def.into(), None))
46 },
47 ast::StructDef(it) => {
48 let src = hir::Source { file_id, ast: it };
49 let def = hir::Struct::from_source(db, src)?;
50 Some(from_module_def(db, def.into(), None))
51 },
52 ast::EnumDef(it) => {
53 let src = hir::Source { file_id, ast: it };
54 let def = hir::Enum::from_source(db, src)?;
55 Some(from_module_def(db, def.into(), None))
56 },
57 ast::TraitDef(it) => {
58 let src = hir::Source { file_id, ast: it };
59 let def = hir::Trait::from_source(db, src)?;
60 Some(from_module_def(db, def.into(), None))
61 },
62 ast::StaticDef(it) => {
63 let src = hir::Source { file_id, ast: it };
64 let def = hir::Static::from_source(db, src)?;
65 Some(from_module_def(db, def.into(), None))
66 },
67 ast::EnumVariant(it) => {
68 let src = hir::Source { file_id, ast: it };
69 let def = hir::EnumVariant::from_source(db, src)?;
70 Some(from_module_def(db, def.into(), None))
71 },
72 ast::FnDef(it) => {
73 let src = hir::Source { file_id, ast: it };
74 let def = hir::Function::from_source(db, src)?;
75 if parent.parent().and_then(ast::ItemList::cast).is_some() {
76 Some(from_assoc_item(db, def.into()))
77 } else {
78 Some(from_module_def(db, def.into(), None))
79 }
80 },
81 ast::ConstDef(it) => {
82 let src = hir::Source { file_id, ast: it };
83 let def = hir::Const::from_source(db, src)?;
84 if parent.parent().and_then(ast::ItemList::cast).is_some() {
85 Some(from_assoc_item(db, def.into()))
86 } else {
87 Some(from_module_def(db, def.into(), None))
88 }
89 },
90 ast::TypeAliasDef(it) => {
91 let src = hir::Source { file_id, ast: it };
92 let def = hir::TypeAlias::from_source(db, src)?;
93 if parent.parent().and_then(ast::ItemList::cast).is_some() {
94 Some(from_assoc_item(db, def.into()))
95 } else {
96 Some(from_module_def(db, def.into(), None))
97 }
98 },
99 _ => None,
100 }
101 }
102}
103
104pub(crate) fn classify_name_ref(
105 db: &RootDatabase,
106 file_id: FileId,
107 name_ref: &ast::NameRef,
108) -> Option<NameDefinition> {
109 use PathResolution::*;
110
111 let parent = name_ref.syntax().parent()?;
112 let analyzer = SourceAnalyzer::new(db, file_id, name_ref.syntax(), None);
113
114 if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
115 tested_by!(goto_definition_works_for_methods);
116 if let Some(func) = analyzer.resolve_method_call(&method_call) {
117 return Some(from_assoc_item(db, func.into()));
118 }
119 }
120
121 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
122 tested_by!(goto_definition_works_for_fields);
123 if let Some(field) = analyzer.resolve_field(&field_expr) {
124 return Some(from_struct_field(db, field));
125 }
126 }
127
128 if let Some(record_field) = ast::RecordField::cast(parent.clone()) {
129 tested_by!(goto_definition_works_for_record_fields);
130 if let Some(record_lit) = record_field.syntax().ancestors().find_map(ast::RecordLit::cast) {
131 let variant_def = analyzer.resolve_record_literal(&record_lit)?;
132 let hir_path = Path::from_name_ref(name_ref);
133 let hir_name = hir_path.as_ident()?;
134 let field = variant_def.field(db, hir_name)?;
135 return Some(from_struct_field(db, field));
136 }
137 }
138
139 let ast = ModuleSource::from_child_node(db, file_id, &parent);
140 let file_id = file_id.into();
141 // FIXME: find correct container and visibility for each case
142 let container = Module::from_definition(db, Source { file_id, ast })?;
143 let visibility = None;
144
145 if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
146 tested_by!(goto_definition_works_for_macros);
147 if let Some(macro_def) = analyzer.resolve_macro_call(db, &macro_call) {
148 let kind = NameKind::Macro(macro_def);
149 return Some(NameDefinition { kind, container, visibility });
150 }
151 }
152
153 let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
154 let resolved = analyzer.resolve_path(db, &path)?;
155 match resolved {
156 Def(def) => Some(from_module_def(db, def, Some(container))),
157 AssocItem(item) => Some(from_assoc_item(db, item)),
158 LocalBinding(Either::A(pat)) => from_pat(db, file_id, pat),
159 LocalBinding(Either::B(par)) => {
160 let kind = NameKind::SelfParam(par);
161 Some(NameDefinition { kind, container, visibility })
162 }
163 GenericParam(par) => {
164 // FIXME: get generic param def
165 let kind = NameKind::GenericParam(par);
166 Some(NameDefinition { kind, container, visibility })
167 }
168 Macro(def) => {
169 let kind = NameKind::Macro(def);
170 Some(NameDefinition { kind, container, visibility })
171 }
172 SelfType(impl_block) => {
173 let ty = impl_block.target_ty(db);
174 let kind = NameKind::SelfType(ty);
175 let container = impl_block.module();
176 Some(NameDefinition { kind, container, visibility })
177 }
178 }
179}
diff --git a/crates/ra_ide_api/src/references/name_definition.rs b/crates/ra_ide_api/src/references/name_definition.rs
new file mode 100644
index 000000000..4580bc789
--- /dev/null
+++ b/crates/ra_ide_api/src/references/name_definition.rs
@@ -0,0 +1,113 @@
1//! `NameDefinition` keeps information about the element we want to search references for.
2//! The element is represented by `NameKind`. It's located inside some `container` and
3//! has a `visibility`, which defines a search scope.
4//! Note that the reference search is possible for not all of the classified items.
5
6use hir::{
7 db::AstDatabase, Adt, AssocItem, DefWithBody, FromSource, HasSource, HirFileId, MacroDef,
8 Module, ModuleDef, StructField, Ty, VariantDef,
9};
10use ra_syntax::{ast, ast::VisibilityOwner, match_ast, AstNode, AstPtr};
11
12use crate::db::RootDatabase;
13
14#[derive(Debug, PartialEq, Eq)]
15pub enum NameKind {
16 Macro(MacroDef),
17 Field(StructField),
18 AssocItem(AssocItem),
19 Def(ModuleDef),
20 SelfType(Ty),
21 Pat((DefWithBody, AstPtr<ast::BindPat>)),
22 SelfParam(AstPtr<ast::SelfParam>),
23 GenericParam(u32),
24}
25
26#[derive(PartialEq, Eq)]
27pub(crate) struct NameDefinition {
28 pub visibility: Option<ast::Visibility>,
29 pub container: Module,
30 pub kind: NameKind,
31}
32
33pub(super) fn from_pat(
34 db: &RootDatabase,
35 file_id: HirFileId,
36 pat: AstPtr<ast::BindPat>,
37) -> Option<NameDefinition> {
38 let root = db.parse_or_expand(file_id)?;
39 let def = pat.to_node(&root).syntax().ancestors().find_map(|node| {
40 match_ast! {
41 match node {
42 ast::FnDef(it) => {
43 let src = hir::Source { file_id, ast: it };
44 Some(hir::Function::from_source(db, src)?.into())
45 },
46 ast::ConstDef(it) => {
47 let src = hir::Source { file_id, ast: it };
48 Some(hir::Const::from_source(db, src)?.into())
49 },
50 ast::StaticDef(it) => {
51 let src = hir::Source { file_id, ast: it };
52 Some(hir::Static::from_source(db, src)?.into())
53 },
54 _ => None,
55 }
56 }
57 })?;
58 let kind = NameKind::Pat((def, pat));
59 let container = def.module(db);
60 Some(NameDefinition { kind, container, visibility: None })
61}
62
63pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition {
64 let container = item.module(db);
65 let visibility = match item {
66 AssocItem::Function(f) => f.source(db).ast.visibility(),
67 AssocItem::Const(c) => c.source(db).ast.visibility(),
68 AssocItem::TypeAlias(a) => a.source(db).ast.visibility(),
69 };
70 let kind = NameKind::AssocItem(item);
71 NameDefinition { kind, container, visibility }
72}
73
74pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition {
75 let kind = NameKind::Field(field);
76 let parent = field.parent_def(db);
77 let container = parent.module(db);
78 let visibility = match parent {
79 VariantDef::Struct(s) => s.source(db).ast.visibility(),
80 VariantDef::EnumVariant(e) => e.source(db).ast.parent_enum().visibility(),
81 };
82 NameDefinition { kind, container, visibility }
83}
84
85pub(super) fn from_module_def(
86 db: &RootDatabase,
87 def: ModuleDef,
88 module: Option<Module>,
89) -> NameDefinition {
90 let kind = NameKind::Def(def);
91 let (container, visibility) = match def {
92 ModuleDef::Module(it) => {
93 let container = it.parent(db).or_else(|| Some(it)).unwrap();
94 let visibility = it.declaration_source(db).and_then(|s| s.ast.visibility());
95 (container, visibility)
96 }
97 ModuleDef::EnumVariant(it) => {
98 let container = it.module(db);
99 let visibility = it.source(db).ast.parent_enum().visibility();
100 (container, visibility)
101 }
102 ModuleDef::Function(it) => (it.module(db), it.source(db).ast.visibility()),
103 ModuleDef::Const(it) => (it.module(db), it.source(db).ast.visibility()),
104 ModuleDef::Static(it) => (it.module(db), it.source(db).ast.visibility()),
105 ModuleDef::Trait(it) => (it.module(db), it.source(db).ast.visibility()),
106 ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).ast.visibility()),
107 ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).ast.visibility()),
108 ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).ast.visibility()),
109 ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).ast.visibility()),
110 ModuleDef::BuiltinType(..) => (module.unwrap(), None),
111 };
112 NameDefinition { kind, container, visibility }
113}
diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs
new file mode 100644
index 000000000..0e2e088e0
--- /dev/null
+++ b/crates/ra_ide_api/src/references/rename.rs
@@ -0,0 +1,469 @@
1//! FIXME: write short doc here
2
3use hir::ModuleSource;
4use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode};
6use relative_path::{RelativePath, RelativePathBuf};
7
8use crate::{
9 db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange,
10 SourceFileEdit, TextRange,
11};
12
13use super::find_all_refs;
14
15pub(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
32fn 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
41fn source_edit_from_file_id_range(
42 file_id: FileId,
43 range: TextRange,
44 new_name: &str,
45) -> SourceFileEdit {
46 SourceFileEdit {
47 file_id,
48 edit: {
49 let mut builder = ra_text_edit::TextEditBuilder::default();
50 builder.replace(range, new_name.into());
51 builder.finish()
52 },
53 }
54}
55
56fn rename_mod(
57 db: &RootDatabase,
58 ast_name: &ast::Name,
59 ast_module: &ast::Module,
60 position: FilePosition,
61 new_name: &str,
62) -> Option<SourceChange> {
63 let mut source_file_edits = Vec::new();
64 let mut file_system_edits = Vec::new();
65 let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() };
66 if let Some(module) = hir::Module::from_declaration(db, module_src) {
67 let src = module.definition_source(db);
68 let file_id = src.file_id.original_file(db);
69 match src.ast {
70 ModuleSource::SourceFile(..) => {
71 let mod_path: RelativePathBuf = db.file_relative_path(file_id);
72 // mod is defined in path/to/dir/mod.rs
73 let dst_path = if mod_path.file_stem() == Some("mod") {
74 mod_path
75 .parent()
76 .and_then(|p| p.parent())
77 .or_else(|| Some(RelativePath::new("")))
78 .map(|p| p.join(new_name).join("mod.rs"))
79 } else {
80 Some(mod_path.with_file_name(new_name).with_extension("rs"))
81 };
82 if let Some(path) = dst_path {
83 let move_file = FileSystemEdit::MoveFile {
84 src: file_id,
85 dst_source_root: db.file_source_root(position.file_id),
86 dst_path: path,
87 };
88 file_system_edits.push(move_file);
89 }
90 }
91 ModuleSource::Module(..) => {}
92 }
93 }
94
95 let edit = SourceFileEdit {
96 file_id: position.file_id,
97 edit: {
98 let mut builder = ra_text_edit::TextEditBuilder::default();
99 builder.replace(ast_name.syntax().text_range(), new_name.into());
100 builder.finish()
101 },
102 };
103 source_file_edits.push(edit);
104
105 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
106}
107
108fn rename_reference(
109 db: &RootDatabase,
110 position: FilePosition,
111 new_name: &str,
112) -> Option<RangeInfo<SourceChange>> {
113 let RangeInfo { range, info: refs } = find_all_refs(db, position)?;
114
115 let edit = refs
116 .into_iter()
117 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name))
118 .collect::<Vec<_>>();
119
120 if edit.is_empty() {
121 return None;
122 }
123
124 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit)))
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::{
130 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
131 ReferenceSearchResult,
132 };
133 use insta::assert_debug_snapshot;
134 use test_utils::assert_eq_text;
135
136 #[test]
137 fn test_find_all_refs_for_local() {
138 let code = r#"
139 fn main() {
140 let mut i = 1;
141 let j = 1;
142 i = i<|> + j;
143
144 {
145 i = 0;
146 }
147
148 i = 5;
149 }"#;
150
151 let refs = get_all_refs(code);
152 assert_eq!(refs.len(), 5);
153 }
154
155 #[test]
156 fn test_find_all_refs_for_param_inside() {
157 let code = r#"
158 fn foo(i : u32) -> u32 {
159 i<|>
160 }"#;
161
162 let refs = get_all_refs(code);
163 assert_eq!(refs.len(), 2);
164 }
165
166 #[test]
167 fn test_find_all_refs_for_fn_param() {
168 let code = r#"
169 fn foo(i<|> : u32) -> u32 {
170 i
171 }"#;
172
173 let refs = get_all_refs(code);
174 assert_eq!(refs.len(), 2);
175 }
176
177 #[test]
178 fn test_find_all_refs_field_name() {
179 let code = r#"
180 //- /lib.rs
181 struct Foo {
182 pub spam<|>: u32,
183 }
184
185 fn main(s: Foo) {
186 let f = s.spam;
187 }
188 "#;
189
190 let refs = get_all_refs(code);
191 assert_eq!(refs.len(), 2);
192 }
193
194 #[test]
195 fn test_find_all_refs_impl_item_name() {
196 let code = r#"
197 //- /lib.rs
198 struct Foo;
199 impl Foo {
200 fn f<|>(&self) { }
201 }
202 "#;
203
204 let refs = get_all_refs(code);
205 assert_eq!(refs.len(), 1);
206 }
207
208 #[test]
209 fn test_find_all_refs_enum_var_name() {
210 let code = r#"
211 //- /lib.rs
212 enum Foo {
213 A,
214 B<|>,
215 C,
216 }
217 "#;
218
219 let refs = get_all_refs(code);
220 assert_eq!(refs.len(), 1);
221 }
222
223 #[test]
224 fn test_find_all_refs_modules() {
225 let code = r#"
226 //- /lib.rs
227 pub mod foo;
228 pub mod bar;
229
230 fn f() {
231 let i = foo::Foo { n: 5 };
232 }
233
234 //- /foo.rs
235 use crate::bar;
236
237 pub struct Foo {
238 pub n: u32,
239 }
240
241 fn f() {
242 let i = bar::Bar { n: 5 };
243 }
244
245 //- /bar.rs
246 use crate::foo;
247
248 pub struct Bar {
249 pub n: u32,
250 }
251
252 fn f() {
253 let i = foo::Foo<|> { n: 5 };
254 }
255 "#;
256
257 let (analysis, pos) = analysis_and_position(code);
258 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
259 assert_eq!(refs.len(), 3);
260 }
261
262 fn get_all_refs(text: &str) -> ReferenceSearchResult {
263 let (analysis, position) = single_file_with_position(text);
264 analysis.find_all_refs(position).unwrap().unwrap()
265 }
266
267 #[test]
268 fn test_rename_for_local() {
269 test_rename(
270 r#"
271 fn main() {
272 let mut i = 1;
273 let j = 1;
274 i = i<|> + j;
275
276 {
277 i = 0;
278 }
279
280 i = 5;
281 }"#,
282 "k",
283 r#"
284 fn main() {
285 let mut k = 1;
286 let j = 1;
287 k = k + j;
288
289 {
290 k = 0;
291 }
292
293 k = 5;
294 }"#,
295 );
296 }
297
298 #[test]
299 fn test_rename_for_param_inside() {
300 test_rename(
301 r#"
302 fn foo(i : u32) -> u32 {
303 i<|>
304 }"#,
305 "j",
306 r#"
307 fn foo(j : u32) -> u32 {
308 j
309 }"#,
310 );
311 }
312
313 #[test]
314 fn test_rename_refs_for_fn_param() {
315 test_rename(
316 r#"
317 fn foo(i<|> : u32) -> u32 {
318 i
319 }"#,
320 "new_name",
321 r#"
322 fn foo(new_name : u32) -> u32 {
323 new_name
324 }"#,
325 );
326 }
327
328 #[test]
329 fn test_rename_for_mut_param() {
330 test_rename(
331 r#"
332 fn foo(mut i<|> : u32) -> u32 {
333 i
334 }"#,
335 "new_name",
336 r#"
337 fn foo(mut new_name : u32) -> u32 {
338 new_name
339 }"#,
340 );
341 }
342
343 #[test]
344 fn test_rename_mod() {
345 let (analysis, position) = analysis_and_position(
346 "
347 //- /lib.rs
348 mod bar;
349
350 //- /bar.rs
351 mod foo<|>;
352
353 //- /bar/foo.rs
354 // emtpy
355 ",
356 );
357 let new_name = "foo2";
358 let source_change = analysis.rename(position, new_name).unwrap();
359 assert_debug_snapshot!(&source_change,
360@r###"
361 Some(
362 RangeInfo {
363 range: [4; 7),
364 info: SourceChange {
365 label: "rename",
366 source_file_edits: [
367 SourceFileEdit {
368 file_id: FileId(
369 2,
370 ),
371 edit: TextEdit {
372 atoms: [
373 AtomTextEdit {
374 delete: [4; 7),
375 insert: "foo2",
376 },
377 ],
378 },
379 },
380 ],
381 file_system_edits: [
382 MoveFile {
383 src: FileId(
384 3,
385 ),
386 dst_source_root: SourceRootId(
387 0,
388 ),
389 dst_path: "bar/foo2.rs",
390 },
391 ],
392 cursor_position: None,
393 },
394 },
395 )
396 "###);
397 }
398
399 #[test]
400 fn test_rename_mod_in_dir() {
401 let (analysis, position) = analysis_and_position(
402 "
403 //- /lib.rs
404 mod fo<|>o;
405 //- /foo/mod.rs
406 // emtpy
407 ",
408 );
409 let new_name = "foo2";
410 let source_change = analysis.rename(position, new_name).unwrap();
411 assert_debug_snapshot!(&source_change,
412 @r###"
413 Some(
414 RangeInfo {
415 range: [4; 7),
416 info: SourceChange {
417 label: "rename",
418 source_file_edits: [
419 SourceFileEdit {
420 file_id: FileId(
421 1,
422 ),
423 edit: TextEdit {
424 atoms: [
425 AtomTextEdit {
426 delete: [4; 7),
427 insert: "foo2",
428 },
429 ],
430 },
431 },
432 ],
433 file_system_edits: [
434 MoveFile {
435 src: FileId(
436 2,
437 ),
438 dst_source_root: SourceRootId(
439 0,
440 ),
441 dst_path: "foo2/mod.rs",
442 },
443 ],
444 cursor_position: None,
445 },
446 },
447 )
448 "###
449 );
450 }
451
452 fn test_rename(text: &str, new_name: &str, expected: &str) {
453 let (analysis, position) = single_file_with_position(text);
454 let source_change = analysis.rename(position, new_name).unwrap();
455 let mut text_edit_builder = ra_text_edit::TextEditBuilder::default();
456 let mut file_id: Option<FileId> = None;
457 if let Some(change) = source_change {
458 for edit in change.info.source_file_edits {
459 file_id = Some(edit.file_id);
460 for atom in edit.edit.as_atoms() {
461 text_edit_builder.replace(atom.delete, atom.insert.clone());
462 }
463 }
464 }
465 let result =
466 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
467 assert_eq_text!(expected, &*result);
468 }
469}
diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs
new file mode 100644
index 000000000..5cb69b8fc
--- /dev/null
+++ b/crates/ra_ide_api/src/references/search_scope.rs
@@ -0,0 +1,92 @@
1//! Generally, `search_scope` returns files that might contain references for the element.
2//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
3//! In some cases, the location of the references is known to within a `TextRange`,
4//! e.g. for things like local variables.
5
6use hir::{DefWithBody, HasSource, ModuleSource};
7use ra_db::{FileId, SourceDatabase, SourceDatabaseExt};
8use ra_syntax::{AstNode, TextRange};
9use rustc_hash::FxHashSet;
10
11use crate::db::RootDatabase;
12
13use super::{NameDefinition, NameKind};
14
15impl NameDefinition {
16 pub(crate) fn search_scope(&self, db: &RootDatabase) -> FxHashSet<(FileId, Option<TextRange>)> {
17 let module_src = self.container.definition_source(db);
18 let file_id = module_src.file_id.original_file(db);
19
20 if let NameKind::Pat((def, _)) = self.kind {
21 let mut res = FxHashSet::default();
22 let range = match def {
23 DefWithBody::Function(f) => f.source(db).ast.syntax().text_range(),
24 DefWithBody::Const(c) => c.source(db).ast.syntax().text_range(),
25 DefWithBody::Static(s) => s.source(db).ast.syntax().text_range(),
26 };
27 res.insert((file_id, Some(range)));
28 return res;
29 }
30
31 let vis =
32 self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string());
33
34 if vis.as_str() == "pub(super)" {
35 if let Some(parent_module) = self.container.parent(db) {
36 let mut files = FxHashSet::default();
37 let parent_src = parent_module.definition_source(db);
38 let file_id = parent_src.file_id.original_file(db);
39
40 match parent_src.ast {
41 ModuleSource::Module(m) => {
42 let range = Some(m.syntax().text_range());
43 files.insert((file_id, range));
44 }
45 ModuleSource::SourceFile(_) => {
46 files.insert((file_id, None));
47 files.extend(parent_module.children(db).map(|m| {
48 let src = m.definition_source(db);
49 (src.file_id.original_file(db), None)
50 }));
51 }
52 }
53 return files;
54 }
55 }
56
57 if vis.as_str() != "" {
58 let source_root_id = db.file_source_root(file_id);
59 let source_root = db.source_root(source_root_id);
60 let mut files =
61 source_root.walk().map(|id| (id.into(), None)).collect::<FxHashSet<_>>();
62
63 // FIXME: add "pub(in path)"
64
65 if vis.as_str() == "pub(crate)" {
66 return files;
67 }
68 if vis.as_str() == "pub" {
69 let krate = self.container.krate(db).unwrap();
70 let crate_graph = db.crate_graph();
71 for crate_id in crate_graph.iter() {
72 let mut crate_deps = crate_graph.dependencies(crate_id);
73 if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) {
74 let root_file = crate_graph.crate_root(crate_id);
75 let source_root_id = db.file_source_root(root_file);
76 let source_root = db.source_root(source_root_id);
77 files.extend(source_root.walk().map(|id| (id.into(), None)));
78 }
79 }
80 return files;
81 }
82 }
83
84 let mut res = FxHashSet::default();
85 let range = match module_src.ast {
86 ModuleSource::Module(m) => Some(m.syntax().text_range()),
87 ModuleSource::SourceFile(_) => None,
88 };
89 res.insert((file_id, range));
90 res
91 }
92}
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs
index 1d290387c..33f3caceb 100644
--- a/crates/ra_ide_api/src/syntax_highlighting.rs
+++ b/crates/ra_ide_api/src/syntax_highlighting.rs
@@ -14,7 +14,7 @@ use ra_syntax::{
14 14
15use crate::{ 15use crate::{
16 db::RootDatabase, 16 db::RootDatabase,
17 name_ref_kind::{classify_name_ref, NameRefKind::*}, 17 references::{classify_name_ref, NameKind::*},
18 FileId, 18 FileId,
19}; 19};
20 20
@@ -101,12 +101,10 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
101 continue; 101 continue;
102 } 102 }
103 if let Some(name_ref) = node.as_node().cloned().and_then(ast::NameRef::cast) { 103 if let Some(name_ref) = node.as_node().cloned().and_then(ast::NameRef::cast) {
104 // FIXME: try to reuse the SourceAnalyzers 104 let name_kind = classify_name_ref(db, file_id, &name_ref).map(|d| d.kind);
105 let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); 105 match name_kind {
106 match classify_name_ref(db, &analyzer, &name_ref) {
107 Some(Method(_)) => "function",
108 Some(Macro(_)) => "macro", 106 Some(Macro(_)) => "macro",
109 Some(FieldAccess(_)) => "field", 107 Some(Field(_)) => "field",
110 Some(AssocItem(hir::AssocItem::Function(_))) => "function", 108 Some(AssocItem(hir::AssocItem::Function(_))) => "function",
111 Some(AssocItem(hir::AssocItem::Const(_))) => "constant", 109 Some(AssocItem(hir::AssocItem::Const(_))) => "constant",
112 Some(AssocItem(hir::AssocItem::TypeAlias(_))) => "type", 110 Some(AssocItem(hir::AssocItem::TypeAlias(_))) => "type",
@@ -120,7 +118,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
120 Some(Def(hir::ModuleDef::TypeAlias(_))) => "type", 118 Some(Def(hir::ModuleDef::TypeAlias(_))) => "type",
121 Some(Def(hir::ModuleDef::BuiltinType(_))) => "type", 119 Some(Def(hir::ModuleDef::BuiltinType(_))) => "type",
122 Some(SelfType(_)) => "type", 120 Some(SelfType(_)) => "type",
123 Some(Pat(ptr)) => { 121 Some(Pat((_, ptr))) => {
124 let pat = ptr.to_node(&root); 122 let pat = ptr.to_node(&root);
125 if let Some(name) = pat.name() { 123 if let Some(name) = pat.name() {
126 let text = name.text(); 124 let text = name.text();
@@ -130,6 +128,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
130 Some(calc_binding_hash(file_id, &text, *shadow_count)) 128 Some(calc_binding_hash(file_id, &text, *shadow_count))
131 } 129 }
132 130
131 let analyzer =
132 hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None);
133 if is_variable_mutable(db, &analyzer, ptr.to_node(&root)) { 133 if is_variable_mutable(db, &analyzer, ptr.to_node(&root)) {
134 "variable.mut" 134 "variable.mut"
135 } else { 135 } else {
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index af3cd04ea..307082865 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -481,7 +481,6 @@ pub fn handle_references(
481 params: req::ReferenceParams, 481 params: req::ReferenceParams,
482) -> Result<Option<Vec<Location>>> { 482) -> Result<Option<Vec<Location>>> {
483 let position = params.text_document_position.try_conv_with(&world)?; 483 let position = params.text_document_position.try_conv_with(&world)?;
484 let line_index = world.analysis().file_line_index(position.file_id)?;
485 484
486 let refs = match world.analysis().find_all_refs(position)? { 485 let refs = match world.analysis().find_all_refs(position)? {
487 None => return Ok(None), 486 None => return Ok(None),
@@ -490,13 +489,19 @@ pub fn handle_references(
490 489
491 let locations = if params.context.include_declaration { 490 let locations = if params.context.include_declaration {
492 refs.into_iter() 491 refs.into_iter()
493 .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok()) 492 .filter_map(|r| {
493 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
494 to_location(r.file_id, r.range, &world, &line_index).ok()
495 })
494 .collect() 496 .collect()
495 } else { 497 } else {
496 // Only iterate over the references if include_declaration was false 498 // Only iterate over the references if include_declaration was false
497 refs.references() 499 refs.references()
498 .iter() 500 .iter()
499 .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok()) 501 .filter_map(|r| {
502 let line_index = world.analysis().file_line_index(r.file_id).ok()?;
503 to_location(r.file_id, r.range, &world, &line_index).ok()
504 })
500 .collect() 505 .collect()
501 }; 506 };
502 507
@@ -746,6 +751,7 @@ pub fn handle_document_highlight(
746 751
747 Ok(Some( 752 Ok(Some(
748 refs.into_iter() 753 refs.into_iter()
754 .filter(|r| r.file_id == file_id)
749 .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None }) 755 .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None })
750 .collect(), 756 .collect(),
751 )) 757 ))