diff options
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r-- | crates/ra_ide_api/src/goto_definition.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/name_kind.rs | 287 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references.rs | 417 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references/classify.rs | 143 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references/definition.rs | 177 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references/rename.rs | 467 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references/search_scope.rs (renamed from crates/ra_ide_api/src/search_scope.rs) | 52 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_highlighting.rs | 10 |
10 files changed, 869 insertions, 699 deletions
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index fefd59cdd..c35d8da41 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::{ | |||
10 | use crate::{ | 10 | use crate::{ |
11 | db::RootDatabase, | 11 | db::RootDatabase, |
12 | display::ShortLabel, | 12 | display::ShortLabel, |
13 | name_kind::{classify_name_ref, NameKind::*}, | 13 | references::{classify_name_ref, NameKind::*}, |
14 | FilePosition, NavigationTarget, RangeInfo, | 14 | FilePosition, NavigationTarget, RangeInfo, |
15 | }; | 15 | }; |
16 | 16 | ||
@@ -54,8 +54,7 @@ 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).and_then(|d| Some(d.item)); |
58 | let name_kind = classify_name_ref(db, file_id, &analyzer, &name_ref).and_then(|d| Some(d.item)); | ||
59 | match name_kind { | 58 | match name_kind { |
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(FieldAccess(field)) => return Exact(NavigationTarget::from_field(db, field)), |
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 316f43c1b..189094ae5 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_kind::{classify_name_ref, NameKind::*}, | 17 | references::{classify_name_ref, NameKind::*}, |
18 | FilePosition, FileRange, RangeInfo, | 18 | FilePosition, FileRange, RangeInfo, |
19 | }; | 19 | }; |
20 | 20 | ||
@@ -99,11 +99,9 @@ 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 | let name_kind = classify_name_ref(db, position.file_id, &analyzer, &name_ref) | 103 | let name_kind = |
106 | .and_then(|d| Some(d.item)); | 104 | classify_name_ref(db, position.file_id, &name_ref).and_then(|d| Some(d.item)); |
107 | match name_kind { | 105 | match name_kind { |
108 | Some(Macro(it)) => { | 106 | Some(Macro(it)) => { |
109 | let src = it.source(db); | 107 | let src = it.source(db); |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 2dc3f0944..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; | |||
19 | mod status; | 19 | mod status; |
20 | mod completion; | 20 | mod completion; |
21 | mod runnables; | 21 | mod runnables; |
22 | mod name_kind; | ||
23 | mod goto_definition; | 22 | mod goto_definition; |
24 | mod goto_type_definition; | 23 | mod goto_type_definition; |
25 | mod extend_selection; | 24 | mod extend_selection; |
@@ -41,7 +40,6 @@ mod matching_brace; | |||
41 | mod display; | 40 | mod display; |
42 | mod inlay_hints; | 41 | mod inlay_hints; |
43 | mod wasm_shims; | 42 | mod wasm_shims; |
44 | mod search_scope; | ||
45 | 43 | ||
46 | #[cfg(test)] | 44 | #[cfg(test)] |
47 | mod marks; | 45 | mod marks; |
diff --git a/crates/ra_ide_api/src/name_kind.rs b/crates/ra_ide_api/src/name_kind.rs deleted file mode 100644 index 583399cfe..000000000 --- a/crates/ra_ide_api/src/name_kind.rs +++ /dev/null | |||
@@ -1,287 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{ | ||
4 | db::AstDatabase, Adt, AssocItem, DefWithBody, Either, EnumVariant, FromSource, HasSource, | ||
5 | HirFileId, MacroDef, Module, ModuleDef, ModuleSource, Path, PathResolution, Source, | ||
6 | SourceAnalyzer, StructField, Ty, VariantDef, | ||
7 | }; | ||
8 | use ra_db::FileId; | ||
9 | use ra_syntax::{ast, ast::VisibilityOwner, match_ast, AstNode, AstPtr}; | ||
10 | |||
11 | use crate::db::RootDatabase; | ||
12 | |||
13 | #[derive(Debug, PartialEq, Eq)] | ||
14 | pub enum NameKind { | ||
15 | Macro(MacroDef), | ||
16 | FieldAccess(StructField), | ||
17 | AssocItem(AssocItem), | ||
18 | Def(ModuleDef), | ||
19 | SelfType(Ty), | ||
20 | Pat((DefWithBody, AstPtr<ast::BindPat>)), | ||
21 | SelfParam(AstPtr<ast::SelfParam>), | ||
22 | GenericParam(u32), | ||
23 | } | ||
24 | |||
25 | #[derive(PartialEq, Eq)] | ||
26 | pub(crate) struct Definition { | ||
27 | pub visibility: Option<ast::Visibility>, | ||
28 | pub container: Module, | ||
29 | pub item: NameKind, | ||
30 | } | ||
31 | |||
32 | trait HasDefinition { | ||
33 | type Def; | ||
34 | type Ref; | ||
35 | |||
36 | fn definition(self, db: &RootDatabase) -> Definition; | ||
37 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition>; | ||
38 | fn from_ref( | ||
39 | db: &RootDatabase, | ||
40 | analyzer: &SourceAnalyzer, | ||
41 | refer: Self::Ref, | ||
42 | ) -> Option<Definition>; | ||
43 | } | ||
44 | |||
45 | pub(crate) fn classify_name_ref( | ||
46 | db: &RootDatabase, | ||
47 | file_id: FileId, | ||
48 | analyzer: &SourceAnalyzer, | ||
49 | name_ref: &ast::NameRef, | ||
50 | ) -> Option<Definition> { | ||
51 | let parent = name_ref.syntax().parent()?; | ||
52 | match_ast! { | ||
53 | match parent { | ||
54 | ast::MethodCallExpr(it) => { | ||
55 | return AssocItem::from_ref(db, analyzer, it); | ||
56 | }, | ||
57 | ast::FieldExpr(it) => { | ||
58 | if let Some(field) = analyzer.resolve_field(&it) { | ||
59 | return Some(field.definition(db)); | ||
60 | } | ||
61 | }, | ||
62 | ast::RecordField(it) => { | ||
63 | if let Some(record_lit) = it.syntax().ancestors().find_map(ast::RecordLit::cast) { | ||
64 | let variant_def = analyzer.resolve_record_literal(&record_lit)?; | ||
65 | let hir_path = Path::from_name_ref(name_ref); | ||
66 | let hir_name = hir_path.as_ident()?; | ||
67 | let field = variant_def.field(db, hir_name)?; | ||
68 | return Some(field.definition(db)); | ||
69 | } | ||
70 | }, | ||
71 | _ => (), | ||
72 | } | ||
73 | } | ||
74 | |||
75 | let ast = ModuleSource::from_child_node(db, file_id, &parent); | ||
76 | let file_id = file_id.into(); | ||
77 | let container = Module::from_definition(db, Source { file_id, ast })?; | ||
78 | let visibility = None; | ||
79 | |||
80 | if let Some(macro_call) = | ||
81 | parent.parent().and_then(|node| node.parent()).and_then(ast::MacroCall::cast) | ||
82 | { | ||
83 | if let Some(mac) = analyzer.resolve_macro_call(db, ¯o_call) { | ||
84 | return Some(Definition { item: NameKind::Macro(mac), container, visibility }); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | // General case, a path or a local: | ||
89 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; | ||
90 | let resolved = analyzer.resolve_path(db, &path)?; | ||
91 | match resolved { | ||
92 | PathResolution::Def(def) => Some(def.definition(db)), | ||
93 | PathResolution::LocalBinding(Either::A(pat)) => decl_from_pat(db, file_id, pat), | ||
94 | PathResolution::LocalBinding(Either::B(par)) => { | ||
95 | Some(Definition { item: NameKind::SelfParam(par), container, visibility }) | ||
96 | } | ||
97 | PathResolution::GenericParam(par) => { | ||
98 | // FIXME: get generic param def | ||
99 | Some(Definition { item: NameKind::GenericParam(par), container, visibility }) | ||
100 | } | ||
101 | PathResolution::Macro(def) => { | ||
102 | Some(Definition { item: NameKind::Macro(def), container, visibility }) | ||
103 | } | ||
104 | PathResolution::SelfType(impl_block) => { | ||
105 | let ty = impl_block.target_ty(db); | ||
106 | let container = impl_block.module(); | ||
107 | Some(Definition { item: NameKind::SelfType(ty), container, visibility }) | ||
108 | } | ||
109 | PathResolution::AssocItem(assoc) => Some(assoc.definition(db)), | ||
110 | } | ||
111 | } | ||
112 | |||
113 | pub(crate) fn classify_name( | ||
114 | db: &RootDatabase, | ||
115 | file_id: FileId, | ||
116 | name: &ast::Name, | ||
117 | ) -> Option<Definition> { | ||
118 | let parent = name.syntax().parent()?; | ||
119 | let file_id = file_id.into(); | ||
120 | |||
121 | match_ast! { | ||
122 | match parent { | ||
123 | ast::BindPat(it) => { | ||
124 | decl_from_pat(db, file_id, AstPtr::new(&it)) | ||
125 | }, | ||
126 | ast::RecordFieldDef(it) => { | ||
127 | StructField::from_def(db, file_id, it) | ||
128 | }, | ||
129 | ast::ImplItem(it) => { | ||
130 | AssocItem::from_def(db, file_id, it.clone()).or_else(|| { | ||
131 | match it { | ||
132 | ast::ImplItem::FnDef(f) => ModuleDef::from_def(db, file_id, f.into()), | ||
133 | ast::ImplItem::ConstDef(c) => ModuleDef::from_def(db, file_id, c.into()), | ||
134 | ast::ImplItem::TypeAliasDef(a) => ModuleDef::from_def(db, file_id, a.into()), | ||
135 | } | ||
136 | }) | ||
137 | }, | ||
138 | ast::EnumVariant(it) => { | ||
139 | let src = hir::Source { file_id, ast: it.clone() }; | ||
140 | let def: ModuleDef = EnumVariant::from_source(db, src)?.into(); | ||
141 | Some(def.definition(db)) | ||
142 | }, | ||
143 | ast::ModuleItem(it) => { | ||
144 | ModuleDef::from_def(db, file_id, it) | ||
145 | }, | ||
146 | _ => None, | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | fn decl_from_pat( | ||
152 | db: &RootDatabase, | ||
153 | file_id: HirFileId, | ||
154 | pat: AstPtr<ast::BindPat>, | ||
155 | ) -> Option<Definition> { | ||
156 | let root = db.parse_or_expand(file_id)?; | ||
157 | // FIXME: use match_ast! | ||
158 | let def = pat.to_node(&root).syntax().ancestors().find_map(|node| { | ||
159 | if let Some(it) = ast::FnDef::cast(node.clone()) { | ||
160 | let src = hir::Source { file_id, ast: it }; | ||
161 | Some(hir::Function::from_source(db, src)?.into()) | ||
162 | } else if let Some(it) = ast::ConstDef::cast(node.clone()) { | ||
163 | let src = hir::Source { file_id, ast: it }; | ||
164 | Some(hir::Const::from_source(db, src)?.into()) | ||
165 | } else if let Some(it) = ast::StaticDef::cast(node.clone()) { | ||
166 | let src = hir::Source { file_id, ast: it }; | ||
167 | Some(hir::Static::from_source(db, src)?.into()) | ||
168 | } else { | ||
169 | None | ||
170 | } | ||
171 | })?; | ||
172 | let item = NameKind::Pat((def, pat)); | ||
173 | let container = def.module(db); | ||
174 | Some(Definition { item, container, visibility: None }) | ||
175 | } | ||
176 | |||
177 | impl HasDefinition for StructField { | ||
178 | type Def = ast::RecordFieldDef; | ||
179 | type Ref = ast::FieldExpr; | ||
180 | |||
181 | fn definition(self, db: &RootDatabase) -> Definition { | ||
182 | let item = NameKind::FieldAccess(self); | ||
183 | let parent = self.parent_def(db); | ||
184 | let container = parent.module(db); | ||
185 | let visibility = match parent { | ||
186 | VariantDef::Struct(s) => s.source(db).ast.visibility(), | ||
187 | VariantDef::EnumVariant(e) => e.source(db).ast.parent_enum().visibility(), | ||
188 | }; | ||
189 | Definition { item, container, visibility } | ||
190 | } | ||
191 | |||
192 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
193 | let src = hir::Source { file_id, ast: hir::FieldSource::Named(def) }; | ||
194 | let field = StructField::from_source(db, src)?; | ||
195 | Some(field.definition(db)) | ||
196 | } | ||
197 | |||
198 | fn from_ref( | ||
199 | db: &RootDatabase, | ||
200 | analyzer: &SourceAnalyzer, | ||
201 | refer: Self::Ref, | ||
202 | ) -> Option<Definition> { | ||
203 | let field = analyzer.resolve_field(&refer)?; | ||
204 | Some(field.definition(db)) | ||
205 | } | ||
206 | } | ||
207 | |||
208 | impl HasDefinition for AssocItem { | ||
209 | type Def = ast::ImplItem; | ||
210 | type Ref = ast::MethodCallExpr; | ||
211 | |||
212 | fn definition(self, db: &RootDatabase) -> Definition { | ||
213 | let item = NameKind::AssocItem(self); | ||
214 | let container = self.module(db); | ||
215 | let visibility = match self { | ||
216 | AssocItem::Function(f) => f.source(db).ast.visibility(), | ||
217 | AssocItem::Const(c) => c.source(db).ast.visibility(), | ||
218 | AssocItem::TypeAlias(a) => a.source(db).ast.visibility(), | ||
219 | }; | ||
220 | Definition { item, container, visibility } | ||
221 | } | ||
222 | |||
223 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
224 | if def.syntax().parent().and_then(ast::ItemList::cast).is_none() { | ||
225 | return None; | ||
226 | } | ||
227 | let src = hir::Source { file_id, ast: def }; | ||
228 | let item = AssocItem::from_source(db, src)?; | ||
229 | Some(item.definition(db)) | ||
230 | } | ||
231 | |||
232 | fn from_ref( | ||
233 | db: &RootDatabase, | ||
234 | analyzer: &SourceAnalyzer, | ||
235 | refer: Self::Ref, | ||
236 | ) -> Option<Definition> { | ||
237 | let func: AssocItem = analyzer.resolve_method_call(&refer)?.into(); | ||
238 | Some(func.definition(db)) | ||
239 | } | ||
240 | } | ||
241 | |||
242 | impl HasDefinition for ModuleDef { | ||
243 | type Def = ast::ModuleItem; | ||
244 | type Ref = ast::Path; | ||
245 | |||
246 | fn definition(self, db: &RootDatabase) -> Definition { | ||
247 | let (container, visibility) = match self { | ||
248 | ModuleDef::Module(it) => { | ||
249 | let container = it.parent(db).or_else(|| Some(it)).unwrap(); | ||
250 | let visibility = it.declaration_source(db).and_then(|s| s.ast.visibility()); | ||
251 | (container, visibility) | ||
252 | } | ||
253 | ModuleDef::EnumVariant(it) => { | ||
254 | let container = it.module(db); | ||
255 | let visibility = it.source(db).ast.parent_enum().visibility(); | ||
256 | (container, visibility) | ||
257 | } | ||
258 | ModuleDef::Function(it) => (it.module(db), it.source(db).ast.visibility()), | ||
259 | ModuleDef::Const(it) => (it.module(db), it.source(db).ast.visibility()), | ||
260 | ModuleDef::Static(it) => (it.module(db), it.source(db).ast.visibility()), | ||
261 | ModuleDef::Trait(it) => (it.module(db), it.source(db).ast.visibility()), | ||
262 | ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).ast.visibility()), | ||
263 | ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
264 | ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
265 | ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
266 | ModuleDef::BuiltinType(..) => unreachable!(), | ||
267 | }; | ||
268 | let item = NameKind::Def(self); | ||
269 | Definition { item, container, visibility } | ||
270 | } | ||
271 | |||
272 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
273 | let src = hir::Source { file_id, ast: def }; | ||
274 | let def = ModuleDef::from_source(db, src)?; | ||
275 | Some(def.definition(db)) | ||
276 | } | ||
277 | |||
278 | fn from_ref( | ||
279 | db: &RootDatabase, | ||
280 | analyzer: &SourceAnalyzer, | ||
281 | refer: Self::Ref, | ||
282 | ) -> Option<Definition> { | ||
283 | None | ||
284 | } | ||
285 | } | ||
286 | |||
287 | // FIXME: impl HasDefinition for hir::MacroDef | ||
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 | ||
3 | use hir::ModuleSource; | 3 | mod classify; |
4 | mod definition; | ||
5 | mod rename; | ||
6 | mod search_scope; | ||
7 | |||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 8 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; | 9 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; |
6 | use relative_path::{RelativePath, RelativePathBuf}; | 10 | |
7 | 11 | use crate::{db::RootDatabase, FileId, FilePosition, FileRange, NavigationTarget, RangeInfo}; | |
8 | use crate::{ | 12 | |
9 | db::RootDatabase, | 13 | pub(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 | ||
105 | pub(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 | ||
122 | fn find_name_and_module_at_offset( | 83 | fn 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 | |||
131 | fn 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 | ||
146 | fn rename_mod( | 99 | fn 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 | |||
198 | fn 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)] |
218 | mod tests { | 138 | mod 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 | } |
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..0b604a5cf --- /dev/null +++ b/crates/ra_ide_api/src/references/classify.rs | |||
@@ -0,0 +1,143 @@ | |||
1 | use hir::{ | ||
2 | AssocItem, Either, EnumVariant, FromSource, Module, ModuleDef, ModuleSource, Path, | ||
3 | PathResolution, Source, SourceAnalyzer, StructField, | ||
4 | }; | ||
5 | use ra_db::FileId; | ||
6 | use ra_syntax::{ast, match_ast, AstNode, AstPtr}; | ||
7 | |||
8 | use super::{definition::HasDefinition, Definition, NameKind}; | ||
9 | use crate::db::RootDatabase; | ||
10 | |||
11 | use hir::{db::AstDatabase, HirFileId}; | ||
12 | |||
13 | pub(crate) fn classify_name( | ||
14 | db: &RootDatabase, | ||
15 | file_id: FileId, | ||
16 | name: &ast::Name, | ||
17 | ) -> Option<Definition> { | ||
18 | let parent = name.syntax().parent()?; | ||
19 | let file_id = file_id.into(); | ||
20 | |||
21 | match_ast! { | ||
22 | match parent { | ||
23 | ast::BindPat(it) => { | ||
24 | decl_from_pat(db, file_id, AstPtr::new(&it)) | ||
25 | }, | ||
26 | ast::RecordFieldDef(it) => { | ||
27 | StructField::from_def(db, file_id, it) | ||
28 | }, | ||
29 | ast::ImplItem(it) => { | ||
30 | AssocItem::from_def(db, file_id, it.clone()).or_else(|| { | ||
31 | match it { | ||
32 | ast::ImplItem::FnDef(f) => ModuleDef::from_def(db, file_id, f.into()), | ||
33 | ast::ImplItem::ConstDef(c) => ModuleDef::from_def(db, file_id, c.into()), | ||
34 | ast::ImplItem::TypeAliasDef(a) => ModuleDef::from_def(db, file_id, a.into()), | ||
35 | } | ||
36 | }) | ||
37 | }, | ||
38 | ast::EnumVariant(it) => { | ||
39 | let src = hir::Source { file_id, ast: it.clone() }; | ||
40 | let def: ModuleDef = EnumVariant::from_source(db, src)?.into(); | ||
41 | Some(def.definition(db)) | ||
42 | }, | ||
43 | ast::ModuleItem(it) => { | ||
44 | ModuleDef::from_def(db, file_id, it) | ||
45 | }, | ||
46 | _ => None, | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | pub(crate) fn classify_name_ref( | ||
52 | db: &RootDatabase, | ||
53 | file_id: FileId, | ||
54 | name_ref: &ast::NameRef, | ||
55 | ) -> Option<Definition> { | ||
56 | let analyzer = SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
57 | let parent = name_ref.syntax().parent()?; | ||
58 | match_ast! { | ||
59 | match parent { | ||
60 | ast::MethodCallExpr(it) => { | ||
61 | return AssocItem::from_ref(db, &analyzer, it); | ||
62 | }, | ||
63 | ast::FieldExpr(it) => { | ||
64 | if let Some(field) = analyzer.resolve_field(&it) { | ||
65 | return Some(field.definition(db)); | ||
66 | } | ||
67 | }, | ||
68 | ast::RecordField(it) => { | ||
69 | if let Some(record_lit) = it.syntax().ancestors().find_map(ast::RecordLit::cast) { | ||
70 | let variant_def = analyzer.resolve_record_literal(&record_lit)?; | ||
71 | let hir_path = Path::from_name_ref(name_ref); | ||
72 | let hir_name = hir_path.as_ident()?; | ||
73 | let field = variant_def.field(db, hir_name)?; | ||
74 | return Some(field.definition(db)); | ||
75 | } | ||
76 | }, | ||
77 | _ => (), | ||
78 | } | ||
79 | } | ||
80 | |||
81 | let ast = ModuleSource::from_child_node(db, file_id, &parent); | ||
82 | let file_id = file_id.into(); | ||
83 | let container = Module::from_definition(db, Source { file_id, ast })?; | ||
84 | let visibility = None; | ||
85 | |||
86 | if let Some(macro_call) = | ||
87 | parent.parent().and_then(|node| node.parent()).and_then(ast::MacroCall::cast) | ||
88 | { | ||
89 | if let Some(mac) = analyzer.resolve_macro_call(db, ¯o_call) { | ||
90 | return Some(Definition { item: NameKind::Macro(mac), container, visibility }); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | // General case, a path or a local: | ||
95 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; | ||
96 | let resolved = analyzer.resolve_path(db, &path)?; | ||
97 | match resolved { | ||
98 | PathResolution::Def(def) => Some(def.definition(db)), | ||
99 | PathResolution::LocalBinding(Either::A(pat)) => decl_from_pat(db, file_id, pat), | ||
100 | PathResolution::LocalBinding(Either::B(par)) => { | ||
101 | Some(Definition { item: NameKind::SelfParam(par), container, visibility }) | ||
102 | } | ||
103 | PathResolution::GenericParam(par) => { | ||
104 | // FIXME: get generic param def | ||
105 | Some(Definition { item: NameKind::GenericParam(par), container, visibility }) | ||
106 | } | ||
107 | PathResolution::Macro(def) => { | ||
108 | Some(Definition { item: NameKind::Macro(def), container, visibility }) | ||
109 | } | ||
110 | PathResolution::SelfType(impl_block) => { | ||
111 | let ty = impl_block.target_ty(db); | ||
112 | let container = impl_block.module(); | ||
113 | Some(Definition { item: NameKind::SelfType(ty), container, visibility }) | ||
114 | } | ||
115 | PathResolution::AssocItem(assoc) => Some(assoc.definition(db)), | ||
116 | } | ||
117 | } | ||
118 | |||
119 | fn decl_from_pat( | ||
120 | db: &RootDatabase, | ||
121 | file_id: HirFileId, | ||
122 | pat: AstPtr<ast::BindPat>, | ||
123 | ) -> Option<Definition> { | ||
124 | let root = db.parse_or_expand(file_id)?; | ||
125 | // FIXME: use match_ast! | ||
126 | let def = pat.to_node(&root).syntax().ancestors().find_map(|node| { | ||
127 | if let Some(it) = ast::FnDef::cast(node.clone()) { | ||
128 | let src = hir::Source { file_id, ast: it }; | ||
129 | Some(hir::Function::from_source(db, src)?.into()) | ||
130 | } else if let Some(it) = ast::ConstDef::cast(node.clone()) { | ||
131 | let src = hir::Source { file_id, ast: it }; | ||
132 | Some(hir::Const::from_source(db, src)?.into()) | ||
133 | } else if let Some(it) = ast::StaticDef::cast(node.clone()) { | ||
134 | let src = hir::Source { file_id, ast: it }; | ||
135 | Some(hir::Static::from_source(db, src)?.into()) | ||
136 | } else { | ||
137 | None | ||
138 | } | ||
139 | })?; | ||
140 | let item = NameKind::Pat((def, pat)); | ||
141 | let container = def.module(db); | ||
142 | Some(Definition { item, container, visibility: None }) | ||
143 | } | ||
diff --git a/crates/ra_ide_api/src/references/definition.rs b/crates/ra_ide_api/src/references/definition.rs new file mode 100644 index 000000000..65b1f8dd7 --- /dev/null +++ b/crates/ra_ide_api/src/references/definition.rs | |||
@@ -0,0 +1,177 @@ | |||
1 | use hir::{ | ||
2 | db::AstDatabase, Adt, AssocItem, DefWithBody, FromSource, HasSource, HirFileId, MacroDef, | ||
3 | Module, ModuleDef, SourceAnalyzer, StructField, Ty, VariantDef, | ||
4 | }; | ||
5 | use ra_syntax::{ast, ast::VisibilityOwner, AstNode, AstPtr}; | ||
6 | |||
7 | use crate::db::RootDatabase; | ||
8 | |||
9 | #[derive(Debug, PartialEq, Eq)] | ||
10 | pub enum NameKind { | ||
11 | Macro(MacroDef), | ||
12 | FieldAccess(StructField), | ||
13 | AssocItem(AssocItem), | ||
14 | Def(ModuleDef), | ||
15 | SelfType(Ty), | ||
16 | Pat((DefWithBody, AstPtr<ast::BindPat>)), | ||
17 | SelfParam(AstPtr<ast::SelfParam>), | ||
18 | GenericParam(u32), | ||
19 | } | ||
20 | |||
21 | #[derive(PartialEq, Eq)] | ||
22 | pub(crate) struct Definition { | ||
23 | pub visibility: Option<ast::Visibility>, | ||
24 | pub container: Module, | ||
25 | pub item: NameKind, | ||
26 | } | ||
27 | |||
28 | pub(super) trait HasDefinition { | ||
29 | type Def; | ||
30 | type Ref; | ||
31 | |||
32 | fn definition(self, db: &RootDatabase) -> Definition; | ||
33 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition>; | ||
34 | fn from_ref( | ||
35 | db: &RootDatabase, | ||
36 | analyzer: &SourceAnalyzer, | ||
37 | refer: Self::Ref, | ||
38 | ) -> Option<Definition>; | ||
39 | } | ||
40 | |||
41 | // fn decl_from_pat( | ||
42 | // db: &RootDatabase, | ||
43 | // file_id: HirFileId, | ||
44 | // pat: AstPtr<ast::BindPat>, | ||
45 | // ) -> Option<Definition> { | ||
46 | // let root = db.parse_or_expand(file_id)?; | ||
47 | // // FIXME: use match_ast! | ||
48 | // let def = pat.to_node(&root).syntax().ancestors().find_map(|node| { | ||
49 | // if let Some(it) = ast::FnDef::cast(node.clone()) { | ||
50 | // let src = hir::Source { file_id, ast: it }; | ||
51 | // Some(hir::Function::from_source(db, src)?.into()) | ||
52 | // } else if let Some(it) = ast::ConstDef::cast(node.clone()) { | ||
53 | // let src = hir::Source { file_id, ast: it }; | ||
54 | // Some(hir::Const::from_source(db, src)?.into()) | ||
55 | // } else if let Some(it) = ast::StaticDef::cast(node.clone()) { | ||
56 | // let src = hir::Source { file_id, ast: it }; | ||
57 | // Some(hir::Static::from_source(db, src)?.into()) | ||
58 | // } else { | ||
59 | // None | ||
60 | // } | ||
61 | // })?; | ||
62 | // let item = NameKind::Pat((def, pat)); | ||
63 | // let container = def.module(db); | ||
64 | // Some(Definition { item, container, visibility: None }) | ||
65 | // } | ||
66 | |||
67 | impl HasDefinition for StructField { | ||
68 | type Def = ast::RecordFieldDef; | ||
69 | type Ref = ast::FieldExpr; | ||
70 | |||
71 | fn definition(self, db: &RootDatabase) -> Definition { | ||
72 | let item = NameKind::FieldAccess(self); | ||
73 | let parent = self.parent_def(db); | ||
74 | let container = parent.module(db); | ||
75 | let visibility = match parent { | ||
76 | VariantDef::Struct(s) => s.source(db).ast.visibility(), | ||
77 | VariantDef::EnumVariant(e) => e.source(db).ast.parent_enum().visibility(), | ||
78 | }; | ||
79 | Definition { item, container, visibility } | ||
80 | } | ||
81 | |||
82 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
83 | let src = hir::Source { file_id, ast: hir::FieldSource::Named(def) }; | ||
84 | let field = StructField::from_source(db, src)?; | ||
85 | Some(field.definition(db)) | ||
86 | } | ||
87 | |||
88 | fn from_ref( | ||
89 | db: &RootDatabase, | ||
90 | analyzer: &SourceAnalyzer, | ||
91 | refer: Self::Ref, | ||
92 | ) -> Option<Definition> { | ||
93 | let field = analyzer.resolve_field(&refer)?; | ||
94 | Some(field.definition(db)) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | impl HasDefinition for AssocItem { | ||
99 | type Def = ast::ImplItem; | ||
100 | type Ref = ast::MethodCallExpr; | ||
101 | |||
102 | fn definition(self, db: &RootDatabase) -> Definition { | ||
103 | let item = NameKind::AssocItem(self); | ||
104 | let container = self.module(db); | ||
105 | let visibility = match self { | ||
106 | AssocItem::Function(f) => f.source(db).ast.visibility(), | ||
107 | AssocItem::Const(c) => c.source(db).ast.visibility(), | ||
108 | AssocItem::TypeAlias(a) => a.source(db).ast.visibility(), | ||
109 | }; | ||
110 | Definition { item, container, visibility } | ||
111 | } | ||
112 | |||
113 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
114 | if def.syntax().parent().and_then(ast::ItemList::cast).is_none() { | ||
115 | return None; | ||
116 | } | ||
117 | let src = hir::Source { file_id, ast: def }; | ||
118 | let item = AssocItem::from_source(db, src)?; | ||
119 | Some(item.definition(db)) | ||
120 | } | ||
121 | |||
122 | fn from_ref( | ||
123 | db: &RootDatabase, | ||
124 | analyzer: &SourceAnalyzer, | ||
125 | refer: Self::Ref, | ||
126 | ) -> Option<Definition> { | ||
127 | let func: AssocItem = analyzer.resolve_method_call(&refer)?.into(); | ||
128 | Some(func.definition(db)) | ||
129 | } | ||
130 | } | ||
131 | |||
132 | impl HasDefinition for ModuleDef { | ||
133 | type Def = ast::ModuleItem; | ||
134 | type Ref = ast::Path; | ||
135 | |||
136 | fn definition(self, db: &RootDatabase) -> Definition { | ||
137 | let (container, visibility) = match self { | ||
138 | ModuleDef::Module(it) => { | ||
139 | let container = it.parent(db).or_else(|| Some(it)).unwrap(); | ||
140 | let visibility = it.declaration_source(db).and_then(|s| s.ast.visibility()); | ||
141 | (container, visibility) | ||
142 | } | ||
143 | ModuleDef::EnumVariant(it) => { | ||
144 | let container = it.module(db); | ||
145 | let visibility = it.source(db).ast.parent_enum().visibility(); | ||
146 | (container, visibility) | ||
147 | } | ||
148 | ModuleDef::Function(it) => (it.module(db), it.source(db).ast.visibility()), | ||
149 | ModuleDef::Const(it) => (it.module(db), it.source(db).ast.visibility()), | ||
150 | ModuleDef::Static(it) => (it.module(db), it.source(db).ast.visibility()), | ||
151 | ModuleDef::Trait(it) => (it.module(db), it.source(db).ast.visibility()), | ||
152 | ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).ast.visibility()), | ||
153 | ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
154 | ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
155 | ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).ast.visibility()), | ||
156 | ModuleDef::BuiltinType(..) => unreachable!(), | ||
157 | }; | ||
158 | let item = NameKind::Def(self); | ||
159 | Definition { item, container, visibility } | ||
160 | } | ||
161 | |||
162 | fn from_def(db: &RootDatabase, file_id: HirFileId, def: Self::Def) -> Option<Definition> { | ||
163 | let src = hir::Source { file_id, ast: def }; | ||
164 | let def = ModuleDef::from_source(db, src)?; | ||
165 | Some(def.definition(db)) | ||
166 | } | ||
167 | |||
168 | fn from_ref( | ||
169 | db: &RootDatabase, | ||
170 | analyzer: &SourceAnalyzer, | ||
171 | refer: Self::Ref, | ||
172 | ) -> Option<Definition> { | ||
173 | None | ||
174 | } | ||
175 | } | ||
176 | |||
177 | // FIXME: impl HasDefinition for hir::MacroDef | ||
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..7e564a40e --- /dev/null +++ b/crates/ra_ide_api/src/references/rename.rs | |||
@@ -0,0 +1,467 @@ | |||
1 | use hir::ModuleSource; | ||
2 | use ra_db::SourceDatabase; | ||
3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; | ||
4 | use relative_path::{RelativePath, RelativePathBuf}; | ||
5 | |||
6 | use crate::{ | ||
7 | db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, | ||
8 | SourceFileEdit, TextRange, | ||
9 | }; | ||
10 | |||
11 | use super::find_all_refs; | ||
12 | |||
13 | pub(crate) fn rename( | ||
14 | db: &RootDatabase, | ||
15 | position: FilePosition, | ||
16 | new_name: &str, | ||
17 | ) -> Option<RangeInfo<SourceChange>> { | ||
18 | let parse = db.parse(position.file_id); | ||
19 | if let Some((ast_name, ast_module)) = | ||
20 | find_name_and_module_at_offset(parse.tree().syntax(), position) | ||
21 | { | ||
22 | let range = ast_name.syntax().text_range(); | ||
23 | rename_mod(db, &ast_name, &ast_module, position, new_name) | ||
24 | .map(|info| RangeInfo::new(range, info)) | ||
25 | } else { | ||
26 | rename_reference(db, position, new_name) | ||
27 | } | ||
28 | } | ||
29 | |||
30 | fn find_name_and_module_at_offset( | ||
31 | syntax: &SyntaxNode, | ||
32 | position: FilePosition, | ||
33 | ) -> Option<(ast::Name, ast::Module)> { | ||
34 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; | ||
35 | let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; | ||
36 | Some((ast_name, ast_module)) | ||
37 | } | ||
38 | |||
39 | fn source_edit_from_file_id_range( | ||
40 | file_id: FileId, | ||
41 | range: TextRange, | ||
42 | new_name: &str, | ||
43 | ) -> SourceFileEdit { | ||
44 | SourceFileEdit { | ||
45 | file_id, | ||
46 | edit: { | ||
47 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
48 | builder.replace(range, new_name.into()); | ||
49 | builder.finish() | ||
50 | }, | ||
51 | } | ||
52 | } | ||
53 | |||
54 | fn rename_mod( | ||
55 | db: &RootDatabase, | ||
56 | ast_name: &ast::Name, | ||
57 | ast_module: &ast::Module, | ||
58 | position: FilePosition, | ||
59 | new_name: &str, | ||
60 | ) -> Option<SourceChange> { | ||
61 | let mut source_file_edits = Vec::new(); | ||
62 | let mut file_system_edits = Vec::new(); | ||
63 | let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; | ||
64 | if let Some(module) = hir::Module::from_declaration(db, module_src) { | ||
65 | let src = module.definition_source(db); | ||
66 | let file_id = src.file_id.original_file(db); | ||
67 | match src.ast { | ||
68 | ModuleSource::SourceFile(..) => { | ||
69 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | ||
70 | // mod is defined in path/to/dir/mod.rs | ||
71 | let dst_path = if mod_path.file_stem() == Some("mod") { | ||
72 | mod_path | ||
73 | .parent() | ||
74 | .and_then(|p| p.parent()) | ||
75 | .or_else(|| Some(RelativePath::new(""))) | ||
76 | .map(|p| p.join(new_name).join("mod.rs")) | ||
77 | } else { | ||
78 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | ||
79 | }; | ||
80 | if let Some(path) = dst_path { | ||
81 | let move_file = FileSystemEdit::MoveFile { | ||
82 | src: file_id, | ||
83 | dst_source_root: db.file_source_root(position.file_id), | ||
84 | dst_path: path, | ||
85 | }; | ||
86 | file_system_edits.push(move_file); | ||
87 | } | ||
88 | } | ||
89 | ModuleSource::Module(..) => {} | ||
90 | } | ||
91 | } | ||
92 | |||
93 | let edit = SourceFileEdit { | ||
94 | file_id: position.file_id, | ||
95 | edit: { | ||
96 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
97 | builder.replace(ast_name.syntax().text_range(), new_name.into()); | ||
98 | builder.finish() | ||
99 | }, | ||
100 | }; | ||
101 | source_file_edits.push(edit); | ||
102 | |||
103 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | ||
104 | } | ||
105 | |||
106 | fn rename_reference( | ||
107 | db: &RootDatabase, | ||
108 | position: FilePosition, | ||
109 | new_name: &str, | ||
110 | ) -> Option<RangeInfo<SourceChange>> { | ||
111 | let RangeInfo { range, info: refs } = find_all_refs(db, position)?; | ||
112 | |||
113 | let edit = refs | ||
114 | .into_iter() | ||
115 | .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) | ||
116 | .collect::<Vec<_>>(); | ||
117 | |||
118 | if edit.is_empty() { | ||
119 | return None; | ||
120 | } | ||
121 | |||
122 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | ||
123 | } | ||
124 | |||
125 | #[cfg(test)] | ||
126 | mod tests { | ||
127 | use crate::{ | ||
128 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | ||
129 | ReferenceSearchResult, | ||
130 | }; | ||
131 | use insta::assert_debug_snapshot; | ||
132 | use test_utils::assert_eq_text; | ||
133 | |||
134 | #[test] | ||
135 | fn test_find_all_refs_for_local() { | ||
136 | let code = r#" | ||
137 | fn main() { | ||
138 | let mut i = 1; | ||
139 | let j = 1; | ||
140 | i = i<|> + j; | ||
141 | |||
142 | { | ||
143 | i = 0; | ||
144 | } | ||
145 | |||
146 | i = 5; | ||
147 | }"#; | ||
148 | |||
149 | let refs = get_all_refs(code); | ||
150 | assert_eq!(refs.len(), 5); | ||
151 | } | ||
152 | |||
153 | #[test] | ||
154 | fn test_find_all_refs_for_param_inside() { | ||
155 | let code = r#" | ||
156 | fn foo(i : u32) -> u32 { | ||
157 | i<|> | ||
158 | }"#; | ||
159 | |||
160 | let refs = get_all_refs(code); | ||
161 | assert_eq!(refs.len(), 2); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn test_find_all_refs_for_fn_param() { | ||
166 | let code = r#" | ||
167 | fn foo(i<|> : u32) -> u32 { | ||
168 | i | ||
169 | }"#; | ||
170 | |||
171 | let refs = get_all_refs(code); | ||
172 | assert_eq!(refs.len(), 2); | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn test_find_all_refs_field_name() { | ||
177 | let code = r#" | ||
178 | //- /lib.rs | ||
179 | struct Foo { | ||
180 | pub spam<|>: u32, | ||
181 | } | ||
182 | |||
183 | fn main(s: Foo) { | ||
184 | let f = s.spam; | ||
185 | } | ||
186 | "#; | ||
187 | |||
188 | let refs = get_all_refs(code); | ||
189 | assert_eq!(refs.len(), 2); | ||
190 | } | ||
191 | |||
192 | #[test] | ||
193 | fn test_find_all_refs_impl_item_name() { | ||
194 | let code = r#" | ||
195 | //- /lib.rs | ||
196 | struct Foo; | ||
197 | impl Foo { | ||
198 | fn f<|>(&self) { } | ||
199 | } | ||
200 | "#; | ||
201 | |||
202 | let refs = get_all_refs(code); | ||
203 | assert_eq!(refs.len(), 1); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_find_all_refs_enum_var_name() { | ||
208 | let code = r#" | ||
209 | //- /lib.rs | ||
210 | enum Foo { | ||
211 | A, | ||
212 | B<|>, | ||
213 | C, | ||
214 | } | ||
215 | "#; | ||
216 | |||
217 | let refs = get_all_refs(code); | ||
218 | assert_eq!(refs.len(), 1); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_find_all_refs_modules() { | ||
223 | let code = r#" | ||
224 | //- /lib.rs | ||
225 | pub mod foo; | ||
226 | pub mod bar; | ||
227 | |||
228 | fn f() { | ||
229 | let i = foo::Foo { n: 5 }; | ||
230 | } | ||
231 | |||
232 | //- /foo.rs | ||
233 | use crate::bar; | ||
234 | |||
235 | pub struct Foo { | ||
236 | pub n: u32, | ||
237 | } | ||
238 | |||
239 | fn f() { | ||
240 | let i = bar::Bar { n: 5 }; | ||
241 | } | ||
242 | |||
243 | //- /bar.rs | ||
244 | use crate::foo; | ||
245 | |||
246 | pub struct Bar { | ||
247 | pub n: u32, | ||
248 | } | ||
249 | |||
250 | fn f() { | ||
251 | let i = foo::Foo<|> { n: 5 }; | ||
252 | } | ||
253 | "#; | ||
254 | |||
255 | let (analysis, pos) = analysis_and_position(code); | ||
256 | let refs = analysis.find_all_refs(pos).unwrap().unwrap(); | ||
257 | assert_eq!(refs.len(), 3); | ||
258 | } | ||
259 | |||
260 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | ||
261 | let (analysis, position) = single_file_with_position(text); | ||
262 | analysis.find_all_refs(position).unwrap().unwrap() | ||
263 | } | ||
264 | |||
265 | #[test] | ||
266 | fn test_rename_for_local() { | ||
267 | test_rename( | ||
268 | r#" | ||
269 | fn main() { | ||
270 | let mut i = 1; | ||
271 | let j = 1; | ||
272 | i = i<|> + j; | ||
273 | |||
274 | { | ||
275 | i = 0; | ||
276 | } | ||
277 | |||
278 | i = 5; | ||
279 | }"#, | ||
280 | "k", | ||
281 | r#" | ||
282 | fn main() { | ||
283 | let mut k = 1; | ||
284 | let j = 1; | ||
285 | k = k + j; | ||
286 | |||
287 | { | ||
288 | k = 0; | ||
289 | } | ||
290 | |||
291 | k = 5; | ||
292 | }"#, | ||
293 | ); | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn test_rename_for_param_inside() { | ||
298 | test_rename( | ||
299 | r#" | ||
300 | fn foo(i : u32) -> u32 { | ||
301 | i<|> | ||
302 | }"#, | ||
303 | "j", | ||
304 | r#" | ||
305 | fn foo(j : u32) -> u32 { | ||
306 | j | ||
307 | }"#, | ||
308 | ); | ||
309 | } | ||
310 | |||
311 | #[test] | ||
312 | fn test_rename_refs_for_fn_param() { | ||
313 | test_rename( | ||
314 | r#" | ||
315 | fn foo(i<|> : u32) -> u32 { | ||
316 | i | ||
317 | }"#, | ||
318 | "new_name", | ||
319 | r#" | ||
320 | fn foo(new_name : u32) -> u32 { | ||
321 | new_name | ||
322 | }"#, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_rename_for_mut_param() { | ||
328 | test_rename( | ||
329 | r#" | ||
330 | fn foo(mut i<|> : u32) -> u32 { | ||
331 | i | ||
332 | }"#, | ||
333 | "new_name", | ||
334 | r#" | ||
335 | fn foo(mut new_name : u32) -> u32 { | ||
336 | new_name | ||
337 | }"#, | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn test_rename_mod() { | ||
343 | let (analysis, position) = analysis_and_position( | ||
344 | " | ||
345 | //- /lib.rs | ||
346 | mod bar; | ||
347 | |||
348 | //- /bar.rs | ||
349 | mod foo<|>; | ||
350 | |||
351 | //- /bar/foo.rs | ||
352 | // emtpy | ||
353 | ", | ||
354 | ); | ||
355 | let new_name = "foo2"; | ||
356 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
357 | assert_debug_snapshot!(&source_change, | ||
358 | @r###" | ||
359 | Some( | ||
360 | RangeInfo { | ||
361 | range: [4; 7), | ||
362 | info: SourceChange { | ||
363 | label: "rename", | ||
364 | source_file_edits: [ | ||
365 | SourceFileEdit { | ||
366 | file_id: FileId( | ||
367 | 2, | ||
368 | ), | ||
369 | edit: TextEdit { | ||
370 | atoms: [ | ||
371 | AtomTextEdit { | ||
372 | delete: [4; 7), | ||
373 | insert: "foo2", | ||
374 | }, | ||
375 | ], | ||
376 | }, | ||
377 | }, | ||
378 | ], | ||
379 | file_system_edits: [ | ||
380 | MoveFile { | ||
381 | src: FileId( | ||
382 | 3, | ||
383 | ), | ||
384 | dst_source_root: SourceRootId( | ||
385 | 0, | ||
386 | ), | ||
387 | dst_path: "bar/foo2.rs", | ||
388 | }, | ||
389 | ], | ||
390 | cursor_position: None, | ||
391 | }, | ||
392 | }, | ||
393 | ) | ||
394 | "###); | ||
395 | } | ||
396 | |||
397 | #[test] | ||
398 | fn test_rename_mod_in_dir() { | ||
399 | let (analysis, position) = analysis_and_position( | ||
400 | " | ||
401 | //- /lib.rs | ||
402 | mod fo<|>o; | ||
403 | //- /foo/mod.rs | ||
404 | // emtpy | ||
405 | ", | ||
406 | ); | ||
407 | let new_name = "foo2"; | ||
408 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
409 | assert_debug_snapshot!(&source_change, | ||
410 | @r###" | ||
411 | Some( | ||
412 | RangeInfo { | ||
413 | range: [4; 7), | ||
414 | info: SourceChange { | ||
415 | label: "rename", | ||
416 | source_file_edits: [ | ||
417 | SourceFileEdit { | ||
418 | file_id: FileId( | ||
419 | 1, | ||
420 | ), | ||
421 | edit: TextEdit { | ||
422 | atoms: [ | ||
423 | AtomTextEdit { | ||
424 | delete: [4; 7), | ||
425 | insert: "foo2", | ||
426 | }, | ||
427 | ], | ||
428 | }, | ||
429 | }, | ||
430 | ], | ||
431 | file_system_edits: [ | ||
432 | MoveFile { | ||
433 | src: FileId( | ||
434 | 2, | ||
435 | ), | ||
436 | dst_source_root: SourceRootId( | ||
437 | 0, | ||
438 | ), | ||
439 | dst_path: "foo2/mod.rs", | ||
440 | }, | ||
441 | ], | ||
442 | cursor_position: None, | ||
443 | }, | ||
444 | }, | ||
445 | ) | ||
446 | "### | ||
447 | ); | ||
448 | } | ||
449 | |||
450 | fn test_rename(text: &str, new_name: &str, expected: &str) { | ||
451 | let (analysis, position) = single_file_with_position(text); | ||
452 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
453 | let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); | ||
454 | let mut file_id: Option<FileId> = None; | ||
455 | if let Some(change) = source_change { | ||
456 | for edit in change.info.source_file_edits { | ||
457 | file_id = Some(edit.file_id); | ||
458 | for atom in edit.edit.as_atoms() { | ||
459 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | ||
460 | } | ||
461 | } | ||
462 | } | ||
463 | let result = | ||
464 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | ||
465 | assert_eq_text!(expected, &*result); | ||
466 | } | ||
467 | } | ||
diff --git a/crates/ra_ide_api/src/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs index 1590a09c4..557ee7b57 100644 --- a/crates/ra_ide_api/src/search_scope.rs +++ b/crates/ra_ide_api/src/references/search_scope.rs | |||
@@ -1,55 +1,15 @@ | |||
1 | use hir::{DefWithBody, HasSource, ModuleSource, SourceAnalyzer}; | 1 | use hir::{DefWithBody, HasSource, ModuleSource}; |
2 | use ra_db::{FileId, FileRange, SourceDatabase}; | 2 | use ra_db::{FileId, SourceDatabase}; |
3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, TextRange, TextUnit}; | 3 | use ra_syntax::{AstNode, TextRange}; |
4 | 4 | ||
5 | use crate::{ | 5 | use crate::db::RootDatabase; |
6 | db::RootDatabase, | 6 | |
7 | name_kind::{classify_name_ref, Definition, NameKind}, | 7 | use super::{Definition, NameKind}; |
8 | }; | ||
9 | 8 | ||
10 | pub(crate) struct SearchScope { | 9 | pub(crate) struct SearchScope { |
11 | pub scope: Vec<(FileId, Option<TextRange>)>, | 10 | pub scope: Vec<(FileId, Option<TextRange>)>, |
12 | } | 11 | } |
13 | 12 | ||
14 | pub(crate) fn find_refs(db: &RootDatabase, def: Definition, name: String) -> Vec<FileRange> { | ||
15 | let pat = name.as_str(); | ||
16 | let scope = def.scope(db).scope; | ||
17 | let mut refs = vec![]; | ||
18 | |||
19 | let is_match = |file_id: FileId, name_ref: &ast::NameRef| -> bool { | ||
20 | let analyzer = SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
21 | let classified = classify_name_ref(db, file_id, &analyzer, &name_ref); | ||
22 | if let Some(d) = classified { | ||
23 | d == def | ||
24 | } else { | ||
25 | false | ||
26 | } | ||
27 | }; | ||
28 | |||
29 | for (file_id, text_range) in scope { | ||
30 | let text = db.file_text(file_id); | ||
31 | let parse = SourceFile::parse(&text); | ||
32 | let syntax = parse.tree().syntax().clone(); | ||
33 | |||
34 | for (idx, _) in text.match_indices(pat) { | ||
35 | let offset = TextUnit::from_usize(idx); | ||
36 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&syntax, offset) { | ||
37 | let range = name_ref.syntax().text_range(); | ||
38 | |||
39 | if let Some(text_range) = text_range { | ||
40 | if range.is_subrange(&text_range) && is_match(file_id, &name_ref) { | ||
41 | refs.push(FileRange { file_id, range }); | ||
42 | } | ||
43 | } else if is_match(file_id, &name_ref) { | ||
44 | refs.push(FileRange { file_id, range }); | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 | |||
50 | return refs; | ||
51 | } | ||
52 | |||
53 | impl Definition { | 13 | impl Definition { |
54 | pub fn scope(&self, db: &RootDatabase) -> SearchScope { | 14 | pub fn scope(&self, db: &RootDatabase) -> SearchScope { |
55 | let module_src = self.container.definition_source(db); | 15 | let module_src = self.container.definition_source(db); |
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 8be93e27e..28c50102e 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 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | db::RootDatabase, | 16 | db::RootDatabase, |
17 | name_kind::{classify_name_ref, NameKind::*}, | 17 | references::{classify_name_ref, NameKind::*}, |
18 | FileId, | 18 | FileId, |
19 | }; | 19 | }; |
20 | 20 | ||
@@ -101,10 +101,8 @@ 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 = |
105 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | 105 | classify_name_ref(db, file_id, &name_ref).and_then(|d| Some(d.item)); |
106 | let name_kind = classify_name_ref(db, file_id, &analyzer, &name_ref) | ||
107 | .and_then(|d| Some(d.item)); | ||
108 | match name_kind { | 106 | match name_kind { |
109 | Some(Macro(_)) => "macro", | 107 | Some(Macro(_)) => "macro", |
110 | Some(FieldAccess(_)) => "field", | 108 | Some(FieldAccess(_)) => "field", |
@@ -131,6 +129,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
131 | Some(calc_binding_hash(file_id, &text, *shadow_count)) | 129 | Some(calc_binding_hash(file_id, &text, *shadow_count)) |
132 | } | 130 | } |
133 | 131 | ||
132 | let analyzer = | ||
133 | hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
134 | if is_variable_mutable(db, &analyzer, ptr.to_node(&root)) { | 134 | if is_variable_mutable(db, &analyzer, ptr.to_node(&root)) { |
135 | "variable.mut" | 135 | "variable.mut" |
136 | } else { | 136 | } else { |