diff options
Diffstat (limited to 'crates/ra_ide/src/references')
-rw-r--r-- | crates/ra_ide/src/references/classify.rs | 186 | ||||
-rw-r--r-- | crates/ra_ide/src/references/name_definition.rs | 83 | ||||
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 328 | ||||
-rw-r--r-- | crates/ra_ide/src/references/search_scope.rs | 145 |
4 files changed, 742 insertions, 0 deletions
diff --git a/crates/ra_ide/src/references/classify.rs b/crates/ra_ide/src/references/classify.rs new file mode 100644 index 000000000..5cea805ec --- /dev/null +++ b/crates/ra_ide/src/references/classify.rs | |||
@@ -0,0 +1,186 @@ | |||
1 | //! Functions that are used to classify an element from its definition or reference. | ||
2 | |||
3 | use hir::{FromSource, Module, ModuleSource, PathResolution, Source, SourceAnalyzer}; | ||
4 | use ra_prof::profile; | ||
5 | use ra_syntax::{ast, match_ast, AstNode}; | ||
6 | use test_utils::tested_by; | ||
7 | |||
8 | use super::{ | ||
9 | name_definition::{from_assoc_item, from_module_def, from_struct_field}, | ||
10 | NameDefinition, NameKind, | ||
11 | }; | ||
12 | use crate::db::RootDatabase; | ||
13 | |||
14 | pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option<NameDefinition> { | ||
15 | let _p = profile("classify_name"); | ||
16 | let parent = name.value.syntax().parent()?; | ||
17 | |||
18 | match_ast! { | ||
19 | match parent { | ||
20 | ast::BindPat(it) => { | ||
21 | let src = name.with_value(it); | ||
22 | let local = hir::Local::from_source(db, src)?; | ||
23 | Some(NameDefinition { | ||
24 | visibility: None, | ||
25 | container: local.module(db), | ||
26 | kind: NameKind::Local(local), | ||
27 | }) | ||
28 | }, | ||
29 | ast::RecordFieldDef(it) => { | ||
30 | let ast = hir::FieldSource::Named(it); | ||
31 | let src = name.with_value(ast); | ||
32 | let field = hir::StructField::from_source(db, src)?; | ||
33 | Some(from_struct_field(db, field)) | ||
34 | }, | ||
35 | ast::Module(it) => { | ||
36 | let def = { | ||
37 | if !it.has_semi() { | ||
38 | let ast = hir::ModuleSource::Module(it); | ||
39 | let src = name.with_value(ast); | ||
40 | hir::Module::from_definition(db, src) | ||
41 | } else { | ||
42 | let src = name.with_value(it); | ||
43 | hir::Module::from_declaration(db, src) | ||
44 | } | ||
45 | }?; | ||
46 | Some(from_module_def(db, def.into(), None)) | ||
47 | }, | ||
48 | ast::StructDef(it) => { | ||
49 | let src = name.with_value(it); | ||
50 | let def = hir::Struct::from_source(db, src)?; | ||
51 | Some(from_module_def(db, def.into(), None)) | ||
52 | }, | ||
53 | ast::EnumDef(it) => { | ||
54 | let src = name.with_value(it); | ||
55 | let def = hir::Enum::from_source(db, src)?; | ||
56 | Some(from_module_def(db, def.into(), None)) | ||
57 | }, | ||
58 | ast::TraitDef(it) => { | ||
59 | let src = name.with_value(it); | ||
60 | let def = hir::Trait::from_source(db, src)?; | ||
61 | Some(from_module_def(db, def.into(), None)) | ||
62 | }, | ||
63 | ast::StaticDef(it) => { | ||
64 | let src = name.with_value(it); | ||
65 | let def = hir::Static::from_source(db, src)?; | ||
66 | Some(from_module_def(db, def.into(), None)) | ||
67 | }, | ||
68 | ast::EnumVariant(it) => { | ||
69 | let src = name.with_value(it); | ||
70 | let def = hir::EnumVariant::from_source(db, src)?; | ||
71 | Some(from_module_def(db, def.into(), None)) | ||
72 | }, | ||
73 | ast::FnDef(it) => { | ||
74 | let src = name.with_value(it); | ||
75 | let def = hir::Function::from_source(db, src)?; | ||
76 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
77 | Some(from_assoc_item(db, def.into())) | ||
78 | } else { | ||
79 | Some(from_module_def(db, def.into(), None)) | ||
80 | } | ||
81 | }, | ||
82 | ast::ConstDef(it) => { | ||
83 | let src = name.with_value(it); | ||
84 | let def = hir::Const::from_source(db, src)?; | ||
85 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
86 | Some(from_assoc_item(db, def.into())) | ||
87 | } else { | ||
88 | Some(from_module_def(db, def.into(), None)) | ||
89 | } | ||
90 | }, | ||
91 | ast::TypeAliasDef(it) => { | ||
92 | let src = name.with_value(it); | ||
93 | let def = hir::TypeAlias::from_source(db, src)?; | ||
94 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
95 | Some(from_assoc_item(db, def.into())) | ||
96 | } else { | ||
97 | Some(from_module_def(db, def.into(), None)) | ||
98 | } | ||
99 | }, | ||
100 | ast::MacroCall(it) => { | ||
101 | let src = name.with_value(it); | ||
102 | let def = hir::MacroDef::from_source(db, src.clone())?; | ||
103 | |||
104 | let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax())); | ||
105 | let module = Module::from_definition(db, src.with_value(module_src))?; | ||
106 | |||
107 | Some(NameDefinition { | ||
108 | visibility: None, | ||
109 | container: module, | ||
110 | kind: NameKind::Macro(def), | ||
111 | }) | ||
112 | }, | ||
113 | _ => None, | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | pub(crate) fn classify_name_ref( | ||
119 | db: &RootDatabase, | ||
120 | name_ref: Source<&ast::NameRef>, | ||
121 | ) -> Option<NameDefinition> { | ||
122 | let _p = profile("classify_name_ref"); | ||
123 | |||
124 | let parent = name_ref.value.syntax().parent()?; | ||
125 | let analyzer = SourceAnalyzer::new(db, name_ref.map(|it| it.syntax()), None); | ||
126 | |||
127 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { | ||
128 | tested_by!(goto_definition_works_for_methods); | ||
129 | if let Some(func) = analyzer.resolve_method_call(&method_call) { | ||
130 | return Some(from_assoc_item(db, func.into())); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | ||
135 | tested_by!(goto_definition_works_for_fields); | ||
136 | if let Some(field) = analyzer.resolve_field(&field_expr) { | ||
137 | return Some(from_struct_field(db, field)); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | if let Some(record_field) = ast::RecordField::cast(parent.clone()) { | ||
142 | tested_by!(goto_definition_works_for_record_fields); | ||
143 | if let Some(field_def) = analyzer.resolve_record_field(&record_field) { | ||
144 | return Some(from_struct_field(db, field_def)); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | let ast = ModuleSource::from_child_node(db, name_ref.with_value(&parent)); | ||
149 | // FIXME: find correct container and visibility for each case | ||
150 | let container = Module::from_definition(db, name_ref.with_value(ast))?; | ||
151 | let visibility = None; | ||
152 | |||
153 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { | ||
154 | tested_by!(goto_definition_works_for_macros); | ||
155 | if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(¯o_call)) { | ||
156 | let kind = NameKind::Macro(macro_def); | ||
157 | return Some(NameDefinition { kind, container, visibility }); | ||
158 | } | ||
159 | } | ||
160 | |||
161 | let path = name_ref.value.syntax().ancestors().find_map(ast::Path::cast)?; | ||
162 | let resolved = analyzer.resolve_path(db, &path)?; | ||
163 | match resolved { | ||
164 | PathResolution::Def(def) => Some(from_module_def(db, def, Some(container))), | ||
165 | PathResolution::AssocItem(item) => Some(from_assoc_item(db, item)), | ||
166 | PathResolution::Local(local) => { | ||
167 | let container = local.module(db); | ||
168 | let kind = NameKind::Local(local); | ||
169 | Some(NameDefinition { kind, container, visibility: None }) | ||
170 | } | ||
171 | PathResolution::GenericParam(par) => { | ||
172 | // FIXME: get generic param def | ||
173 | let kind = NameKind::GenericParam(par); | ||
174 | Some(NameDefinition { kind, container, visibility }) | ||
175 | } | ||
176 | PathResolution::Macro(def) => { | ||
177 | let kind = NameKind::Macro(def); | ||
178 | Some(NameDefinition { kind, container, visibility }) | ||
179 | } | ||
180 | PathResolution::SelfType(impl_block) => { | ||
181 | let kind = NameKind::SelfType(impl_block); | ||
182 | let container = impl_block.module(db); | ||
183 | Some(NameDefinition { kind, container, visibility }) | ||
184 | } | ||
185 | } | ||
186 | } | ||
diff --git a/crates/ra_ide/src/references/name_definition.rs b/crates/ra_ide/src/references/name_definition.rs new file mode 100644 index 000000000..10d3a2364 --- /dev/null +++ b/crates/ra_ide/src/references/name_definition.rs | |||
@@ -0,0 +1,83 @@ | |||
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 | |||
6 | use hir::{ | ||
7 | Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef, | ||
8 | StructField, VariantDef, | ||
9 | }; | ||
10 | use ra_syntax::{ast, ast::VisibilityOwner}; | ||
11 | |||
12 | use crate::db::RootDatabase; | ||
13 | |||
14 | #[derive(Debug, PartialEq, Eq)] | ||
15 | pub enum NameKind { | ||
16 | Macro(MacroDef), | ||
17 | Field(StructField), | ||
18 | AssocItem(AssocItem), | ||
19 | Def(ModuleDef), | ||
20 | SelfType(ImplBlock), | ||
21 | Local(Local), | ||
22 | GenericParam(GenericParam), | ||
23 | } | ||
24 | |||
25 | #[derive(PartialEq, Eq)] | ||
26 | pub(crate) struct NameDefinition { | ||
27 | pub visibility: Option<ast::Visibility>, | ||
28 | pub container: Module, | ||
29 | pub kind: NameKind, | ||
30 | } | ||
31 | |||
32 | pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition { | ||
33 | let container = item.module(db); | ||
34 | let visibility = match item { | ||
35 | AssocItem::Function(f) => f.source(db).value.visibility(), | ||
36 | AssocItem::Const(c) => c.source(db).value.visibility(), | ||
37 | AssocItem::TypeAlias(a) => a.source(db).value.visibility(), | ||
38 | }; | ||
39 | let kind = NameKind::AssocItem(item); | ||
40 | NameDefinition { kind, container, visibility } | ||
41 | } | ||
42 | |||
43 | pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition { | ||
44 | let kind = NameKind::Field(field); | ||
45 | let parent = field.parent_def(db); | ||
46 | let container = parent.module(db); | ||
47 | let visibility = match parent { | ||
48 | VariantDef::Struct(s) => s.source(db).value.visibility(), | ||
49 | VariantDef::Union(e) => e.source(db).value.visibility(), | ||
50 | VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(), | ||
51 | }; | ||
52 | NameDefinition { kind, container, visibility } | ||
53 | } | ||
54 | |||
55 | pub(super) fn from_module_def( | ||
56 | db: &RootDatabase, | ||
57 | def: ModuleDef, | ||
58 | module: Option<Module>, | ||
59 | ) -> NameDefinition { | ||
60 | let kind = NameKind::Def(def); | ||
61 | let (container, visibility) = match def { | ||
62 | ModuleDef::Module(it) => { | ||
63 | let container = it.parent(db).or_else(|| Some(it)).unwrap(); | ||
64 | let visibility = it.declaration_source(db).and_then(|s| s.value.visibility()); | ||
65 | (container, visibility) | ||
66 | } | ||
67 | ModuleDef::EnumVariant(it) => { | ||
68 | let container = it.module(db); | ||
69 | let visibility = it.source(db).value.parent_enum().visibility(); | ||
70 | (container, visibility) | ||
71 | } | ||
72 | ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()), | ||
73 | ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()), | ||
74 | ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()), | ||
75 | ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()), | ||
76 | ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()), | ||
77 | ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()), | ||
78 | ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()), | ||
79 | ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()), | ||
80 | ModuleDef::BuiltinType(..) => (module.unwrap(), None), | ||
81 | }; | ||
82 | NameDefinition { kind, container, visibility } | ||
83 | } | ||
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 | } | ||
diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs new file mode 100644 index 000000000..f5c9589f4 --- /dev/null +++ b/crates/ra_ide/src/references/search_scope.rs | |||
@@ -0,0 +1,145 @@ | |||
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 | use std::mem; | ||
6 | |||
7 | use hir::{DefWithBody, HasSource, ModuleSource}; | ||
8 | use ra_db::{FileId, SourceDatabase, SourceDatabaseExt}; | ||
9 | use ra_prof::profile; | ||
10 | use ra_syntax::{AstNode, TextRange}; | ||
11 | use rustc_hash::FxHashMap; | ||
12 | |||
13 | use crate::db::RootDatabase; | ||
14 | |||
15 | use super::{NameDefinition, NameKind}; | ||
16 | |||
17 | pub struct SearchScope { | ||
18 | entries: FxHashMap<FileId, Option<TextRange>>, | ||
19 | } | ||
20 | |||
21 | impl SearchScope { | ||
22 | fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope { | ||
23 | SearchScope { entries } | ||
24 | } | ||
25 | pub fn single_file(file: FileId) -> SearchScope { | ||
26 | SearchScope::new(std::iter::once((file, None)).collect()) | ||
27 | } | ||
28 | pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope { | ||
29 | let (mut small, mut large) = (&self.entries, &other.entries); | ||
30 | if small.len() > large.len() { | ||
31 | mem::swap(&mut small, &mut large) | ||
32 | } | ||
33 | |||
34 | let res = small | ||
35 | .iter() | ||
36 | .filter_map(|(file_id, r1)| { | ||
37 | let r2 = large.get(file_id)?; | ||
38 | let r = intersect_ranges(*r1, *r2)?; | ||
39 | Some((*file_id, r)) | ||
40 | }) | ||
41 | .collect(); | ||
42 | return SearchScope::new(res); | ||
43 | |||
44 | fn intersect_ranges( | ||
45 | r1: Option<TextRange>, | ||
46 | r2: Option<TextRange>, | ||
47 | ) -> Option<Option<TextRange>> { | ||
48 | match (r1, r2) { | ||
49 | (None, r) | (r, None) => Some(r), | ||
50 | (Some(r1), Some(r2)) => { | ||
51 | let r = r1.intersection(&r2)?; | ||
52 | Some(Some(r)) | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | impl IntoIterator for SearchScope { | ||
60 | type Item = (FileId, Option<TextRange>); | ||
61 | type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>; | ||
62 | fn into_iter(self) -> Self::IntoIter { | ||
63 | self.entries.into_iter() | ||
64 | } | ||
65 | } | ||
66 | |||
67 | impl NameDefinition { | ||
68 | pub(crate) fn search_scope(&self, db: &RootDatabase) -> SearchScope { | ||
69 | let _p = profile("search_scope"); | ||
70 | |||
71 | let module_src = self.container.definition_source(db); | ||
72 | let file_id = module_src.file_id.original_file(db); | ||
73 | |||
74 | if let NameKind::Local(var) = self.kind { | ||
75 | let range = match var.parent(db) { | ||
76 | DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), | ||
77 | DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), | ||
78 | DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), | ||
79 | }; | ||
80 | let mut res = FxHashMap::default(); | ||
81 | res.insert(file_id, Some(range)); | ||
82 | return SearchScope::new(res); | ||
83 | } | ||
84 | |||
85 | let vis = | ||
86 | self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string()); | ||
87 | |||
88 | if vis.as_str() == "pub(super)" { | ||
89 | if let Some(parent_module) = self.container.parent(db) { | ||
90 | let mut res = FxHashMap::default(); | ||
91 | let parent_src = parent_module.definition_source(db); | ||
92 | let file_id = parent_src.file_id.original_file(db); | ||
93 | |||
94 | match parent_src.value { | ||
95 | ModuleSource::Module(m) => { | ||
96 | let range = Some(m.syntax().text_range()); | ||
97 | res.insert(file_id, range); | ||
98 | } | ||
99 | ModuleSource::SourceFile(_) => { | ||
100 | res.insert(file_id, None); | ||
101 | res.extend(parent_module.children(db).map(|m| { | ||
102 | let src = m.definition_source(db); | ||
103 | (src.file_id.original_file(db), None) | ||
104 | })); | ||
105 | } | ||
106 | } | ||
107 | return SearchScope::new(res); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | if vis.as_str() != "" { | ||
112 | let source_root_id = db.file_source_root(file_id); | ||
113 | let source_root = db.source_root(source_root_id); | ||
114 | let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>(); | ||
115 | |||
116 | // FIXME: add "pub(in path)" | ||
117 | |||
118 | if vis.as_str() == "pub(crate)" { | ||
119 | return SearchScope::new(res); | ||
120 | } | ||
121 | if vis.as_str() == "pub" { | ||
122 | let krate = self.container.krate(); | ||
123 | let crate_graph = db.crate_graph(); | ||
124 | for crate_id in crate_graph.iter() { | ||
125 | let mut crate_deps = crate_graph.dependencies(crate_id); | ||
126 | if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) { | ||
127 | let root_file = crate_graph.crate_root(crate_id); | ||
128 | let source_root_id = db.file_source_root(root_file); | ||
129 | let source_root = db.source_root(source_root_id); | ||
130 | res.extend(source_root.walk().map(|id| (id, None))); | ||
131 | } | ||
132 | } | ||
133 | return SearchScope::new(res); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | let mut res = FxHashMap::default(); | ||
138 | let range = match module_src.value { | ||
139 | ModuleSource::Module(m) => Some(m.syntax().text_range()), | ||
140 | ModuleSource::SourceFile(_) => None, | ||
141 | }; | ||
142 | res.insert(file_id, range); | ||
143 | SearchScope::new(res) | ||
144 | } | ||
145 | } | ||