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