diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-10-23 09:20:18 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-10-23 09:20:18 +0100 |
commit | 4f4fe14fab96c8b40763f9ed5bef51942fd7e504 (patch) | |
tree | cdf5a5b39600b41a9eee301b9760cde1282aeb33 /crates/ra_ide_api/src/references.rs | |
parent | c15ee97fff4324981d03f65210d794664c28f0e4 (diff) | |
parent | decfd28bd14b56befa17257694caacc57a090939 (diff) |
Merge #1892
1892: Find usages r=matklad a=viorina
Fixes #1622.
Co-authored-by: Ekaterina Babshukova <[email protected]>
Diffstat (limited to 'crates/ra_ide_api/src/references.rs')
-rw-r--r-- | crates/ra_ide_api/src/references.rs | 520 |
1 files changed, 195 insertions, 325 deletions
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs index 4247c6d90..f35d835ac 100644 --- a/crates/ra_ide_api/src/references.rs +++ b/crates/ra_ide_api/src/references.rs | |||
@@ -1,13 +1,29 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! This module implements a reference search. |
2 | 2 | //! First, the element at the cursor position must be either an `ast::Name` | |
3 | use hir::{Either, ModuleSource}; | 3 | //! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we |
4 | //! try to resolve the direct tree parent of this element, otherwise we | ||
5 | //! already have a definition and just need to get its HIR together with | ||
6 | //! some information that is needed for futher steps of searching. | ||
7 | //! After that, we collect files that might contain references and look | ||
8 | //! for text occurrences of the identifier. If there's an `ast::NameRef` | ||
9 | //! at the index that the match starts at and its tree parent is | ||
10 | //! resolved to the search element definition, we get a reference. | ||
11 | |||
12 | mod classify; | ||
13 | mod name_definition; | ||
14 | mod rename; | ||
15 | mod search_scope; | ||
16 | |||
17 | use once_cell::unsync::Lazy; | ||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 18 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode}; | 19 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; |
6 | use relative_path::{RelativePath, RelativePathBuf}; | 20 | |
21 | use crate::{db::RootDatabase, FilePosition, FileRange, NavigationTarget, RangeInfo}; | ||
7 | 22 | ||
8 | use crate::{ | 23 | pub(crate) use self::{ |
9 | db::RootDatabase, FileId, FilePosition, FileRange, FileSystemEdit, NavigationTarget, RangeInfo, | 24 | classify::{classify_name, classify_name_ref}, |
10 | SourceChange, SourceFileEdit, TextRange, | 25 | name_definition::{NameDefinition, NameKind}, |
26 | rename::rename, | ||
11 | }; | 27 | }; |
12 | 28 | ||
13 | #[derive(Debug, Clone)] | 29 | #[derive(Debug, Clone)] |
@@ -52,161 +68,82 @@ pub(crate) fn find_all_refs( | |||
52 | position: FilePosition, | 68 | position: FilePosition, |
53 | ) -> Option<RangeInfo<ReferenceSearchResult>> { | 69 | ) -> Option<RangeInfo<ReferenceSearchResult>> { |
54 | let parse = db.parse(position.file_id); | 70 | let parse = db.parse(position.file_id); |
55 | let RangeInfo { range, info: (binding, analyzer) } = find_binding(db, &parse.tree(), position)?; | 71 | let syntax = parse.tree().syntax().clone(); |
56 | let declaration = NavigationTarget::from_bind_pat(position.file_id, &binding); | 72 | let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; |
57 | 73 | ||
58 | let references = analyzer | 74 | let declaration = match def.kind { |
59 | .find_all_refs(&binding) | 75 | NameKind::Macro(mac) => NavigationTarget::from_macro_def(db, mac), |
60 | .into_iter() | 76 | NameKind::Field(field) => NavigationTarget::from_field(db, field), |
61 | .map(move |ref_desc| FileRange { file_id: position.file_id, range: ref_desc.range }) | 77 | NameKind::AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc), |
62 | .collect::<Vec<_>>(); | 78 | NameKind::Def(def) => NavigationTarget::from_def(db, def)?, |
63 | 79 | NameKind::SelfType(ref ty) => match ty.as_adt() { | |
64 | return Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })); | 80 | Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id), |
65 | 81 | None => return None, | |
66 | fn find_binding<'a>( | 82 | }, |
67 | db: &RootDatabase, | 83 | NameKind::Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat), |
68 | source_file: &SourceFile, | 84 | NameKind::SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par), |
69 | position: FilePosition, | 85 | NameKind::GenericParam(_) => return None, |
70 | ) -> Option<RangeInfo<(ast::BindPat, hir::SourceAnalyzer)>> { | 86 | }; |
71 | let syntax = source_file.syntax(); | ||
72 | if let Some(binding) = find_node_at_offset::<ast::BindPat>(syntax, position.offset) { | ||
73 | let range = binding.syntax().text_range(); | ||
74 | let analyzer = hir::SourceAnalyzer::new(db, position.file_id, binding.syntax(), None); | ||
75 | return Some(RangeInfo::new(range, (binding, analyzer))); | ||
76 | }; | ||
77 | let name_ref = find_node_at_offset::<ast::NameRef>(syntax, position.offset)?; | ||
78 | let range = name_ref.syntax().text_range(); | ||
79 | let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None); | ||
80 | let resolved = analyzer.resolve_local_name(&name_ref)?; | ||
81 | if let Either::A(ptr) = resolved.ptr() { | ||
82 | if let ast::Pat::BindPat(binding) = ptr.to_node(source_file.syntax()) { | ||
83 | return Some(RangeInfo::new(range, (binding, analyzer))); | ||
84 | } | ||
85 | } | ||
86 | None | ||
87 | } | ||
88 | } | ||
89 | 87 | ||
90 | pub(crate) fn rename( | 88 | let references = process_definition(db, def, name); |
91 | db: &RootDatabase, | 89 | |
92 | position: FilePosition, | 90 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) |
93 | new_name: &str, | ||
94 | ) -> Option<RangeInfo<SourceChange>> { | ||
95 | let parse = db.parse(position.file_id); | ||
96 | if let Some((ast_name, ast_module)) = | ||
97 | find_name_and_module_at_offset(parse.tree().syntax(), position) | ||
98 | { | ||
99 | let range = ast_name.syntax().text_range(); | ||
100 | rename_mod(db, &ast_name, &ast_module, position, new_name) | ||
101 | .map(|info| RangeInfo::new(range, info)) | ||
102 | } else { | ||
103 | rename_reference(db, position, new_name) | ||
104 | } | ||
105 | } | 91 | } |
106 | 92 | ||
107 | fn find_name_and_module_at_offset( | 93 | fn find_name<'a>( |
94 | db: &RootDatabase, | ||
108 | syntax: &SyntaxNode, | 95 | syntax: &SyntaxNode, |
109 | position: FilePosition, | 96 | position: FilePosition, |
110 | ) -> Option<(ast::Name, ast::Module)> { | 97 | ) -> Option<RangeInfo<(String, NameDefinition)>> { |
111 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; | 98 | if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) { |
112 | let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; | 99 | let def = classify_name(db, position.file_id, &name)?; |
113 | Some((ast_name, ast_module)) | 100 | let range = name.syntax().text_range(); |
114 | } | 101 | return Some(RangeInfo::new(range, (name.text().to_string(), def))); |
115 | |||
116 | fn source_edit_from_file_id_range( | ||
117 | file_id: FileId, | ||
118 | range: TextRange, | ||
119 | new_name: &str, | ||
120 | ) -> SourceFileEdit { | ||
121 | SourceFileEdit { | ||
122 | file_id, | ||
123 | edit: { | ||
124 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
125 | builder.replace(range, new_name.into()); | ||
126 | builder.finish() | ||
127 | }, | ||
128 | } | 102 | } |
103 | let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?; | ||
104 | let def = classify_name_ref(db, position.file_id, &name_ref)?; | ||
105 | let range = name_ref.syntax().text_range(); | ||
106 | Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) | ||
129 | } | 107 | } |
130 | 108 | ||
131 | fn rename_mod( | 109 | fn process_definition(db: &RootDatabase, def: NameDefinition, name: String) -> Vec<FileRange> { |
132 | db: &RootDatabase, | 110 | let pat = name.as_str(); |
133 | ast_name: &ast::Name, | 111 | let scope = def.search_scope(db); |
134 | ast_module: &ast::Module, | 112 | let mut refs = vec![]; |
135 | position: FilePosition, | 113 | |
136 | new_name: &str, | 114 | for (file_id, search_range) in scope { |
137 | ) -> Option<SourceChange> { | 115 | let text = db.file_text(file_id); |
138 | let mut source_file_edits = Vec::new(); | 116 | let parse = Lazy::new(|| SourceFile::parse(&text)); |
139 | let mut file_system_edits = Vec::new(); | 117 | |
140 | let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; | 118 | for (idx, _) in text.match_indices(pat) { |
141 | if let Some(module) = hir::Module::from_declaration(db, module_src) { | 119 | let offset = TextUnit::from_usize(idx); |
142 | let src = module.definition_source(db); | 120 | |
143 | let file_id = src.file_id.original_file(db); | 121 | if let Some(name_ref) = |
144 | match src.ast { | 122 | find_node_at_offset::<ast::NameRef>(parse.tree().syntax(), offset) |
145 | ModuleSource::SourceFile(..) => { | 123 | { |
146 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | 124 | let range = name_ref.syntax().text_range(); |
147 | // mod is defined in path/to/dir/mod.rs | 125 | if let Some(search_range) = search_range { |
148 | let dst_path = if mod_path.file_stem() == Some("mod") { | 126 | if !range.is_subrange(&search_range) { |
149 | mod_path | 127 | continue; |
150 | .parent() | 128 | } |
151 | .and_then(|p| p.parent()) | 129 | } |
152 | .or_else(|| Some(RelativePath::new(""))) | 130 | if let Some(d) = classify_name_ref(db, file_id, &name_ref) { |
153 | .map(|p| p.join(new_name).join("mod.rs")) | 131 | if d == def { |
154 | } else { | 132 | refs.push(FileRange { file_id, range }); |
155 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | 133 | } |
156 | }; | ||
157 | if let Some(path) = dst_path { | ||
158 | let move_file = FileSystemEdit::MoveFile { | ||
159 | src: file_id, | ||
160 | dst_source_root: db.file_source_root(position.file_id), | ||
161 | dst_path: path, | ||
162 | }; | ||
163 | file_system_edits.push(move_file); | ||
164 | } | 134 | } |
165 | } | 135 | } |
166 | ModuleSource::Module(..) => {} | ||
167 | } | 136 | } |
168 | } | 137 | } |
169 | 138 | refs | |
170 | let edit = SourceFileEdit { | ||
171 | file_id: position.file_id, | ||
172 | edit: { | ||
173 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
174 | builder.replace(ast_name.syntax().text_range(), new_name.into()); | ||
175 | builder.finish() | ||
176 | }, | ||
177 | }; | ||
178 | source_file_edits.push(edit); | ||
179 | |||
180 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | ||
181 | } | ||
182 | |||
183 | fn rename_reference( | ||
184 | db: &RootDatabase, | ||
185 | position: FilePosition, | ||
186 | new_name: &str, | ||
187 | ) -> Option<RangeInfo<SourceChange>> { | ||
188 | let RangeInfo { range, info: refs } = find_all_refs(db, position)?; | ||
189 | |||
190 | let edit = refs | ||
191 | .into_iter() | ||
192 | .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) | ||
193 | .collect::<Vec<_>>(); | ||
194 | |||
195 | if edit.is_empty() { | ||
196 | return None; | ||
197 | } | ||
198 | |||
199 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | ||
200 | } | 139 | } |
201 | 140 | ||
202 | #[cfg(test)] | 141 | #[cfg(test)] |
203 | mod tests { | 142 | mod tests { |
204 | use crate::{ | 143 | use crate::{ |
205 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | 144 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, |
206 | ReferenceSearchResult, | 145 | ReferenceSearchResult, |
207 | }; | 146 | }; |
208 | use insta::assert_debug_snapshot; | ||
209 | use test_utils::assert_eq_text; | ||
210 | 147 | ||
211 | #[test] | 148 | #[test] |
212 | fn test_find_all_refs_for_local() { | 149 | fn test_find_all_refs_for_local() { |
@@ -249,211 +186,144 @@ mod tests { | |||
249 | assert_eq!(refs.len(), 2); | 186 | assert_eq!(refs.len(), 2); |
250 | } | 187 | } |
251 | 188 | ||
252 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | ||
253 | let (analysis, position) = single_file_with_position(text); | ||
254 | analysis.find_all_refs(position).unwrap().unwrap() | ||
255 | } | ||
256 | |||
257 | #[test] | 189 | #[test] |
258 | fn test_rename_for_local() { | 190 | fn test_find_all_refs_field_name() { |
259 | test_rename( | 191 | let code = r#" |
260 | r#" | 192 | //- /lib.rs |
261 | fn main() { | 193 | struct Foo { |
262 | let mut i = 1; | 194 | pub spam<|>: u32, |
263 | let j = 1; | 195 | } |
264 | i = i<|> + j; | ||
265 | |||
266 | { | ||
267 | i = 0; | ||
268 | } | ||
269 | |||
270 | i = 5; | ||
271 | }"#, | ||
272 | "k", | ||
273 | r#" | ||
274 | fn main() { | ||
275 | let mut k = 1; | ||
276 | let j = 1; | ||
277 | k = k + j; | ||
278 | 196 | ||
279 | { | 197 | fn main(s: Foo) { |
280 | k = 0; | 198 | let f = s.spam; |
281 | } | 199 | } |
200 | "#; | ||
282 | 201 | ||
283 | k = 5; | 202 | let refs = get_all_refs(code); |
284 | }"#, | 203 | assert_eq!(refs.len(), 2); |
285 | ); | ||
286 | } | 204 | } |
287 | 205 | ||
288 | #[test] | 206 | #[test] |
289 | fn test_rename_for_param_inside() { | 207 | fn test_find_all_refs_impl_item_name() { |
290 | test_rename( | 208 | let code = r#" |
291 | r#" | 209 | //- /lib.rs |
292 | fn foo(i : u32) -> u32 { | 210 | struct Foo; |
293 | i<|> | 211 | impl Foo { |
294 | }"#, | 212 | fn f<|>(&self) { } |
295 | "j", | 213 | } |
296 | r#" | 214 | "#; |
297 | fn foo(j : u32) -> u32 { | ||
298 | j | ||
299 | }"#, | ||
300 | ); | ||
301 | } | ||
302 | 215 | ||
303 | #[test] | 216 | let refs = get_all_refs(code); |
304 | fn test_rename_refs_for_fn_param() { | 217 | assert_eq!(refs.len(), 1); |
305 | test_rename( | ||
306 | r#" | ||
307 | fn foo(i<|> : u32) -> u32 { | ||
308 | i | ||
309 | }"#, | ||
310 | "new_name", | ||
311 | r#" | ||
312 | fn foo(new_name : u32) -> u32 { | ||
313 | new_name | ||
314 | }"#, | ||
315 | ); | ||
316 | } | 218 | } |
317 | 219 | ||
318 | #[test] | 220 | #[test] |
319 | fn test_rename_for_mut_param() { | 221 | fn test_find_all_refs_enum_var_name() { |
320 | test_rename( | 222 | let code = r#" |
321 | r#" | 223 | //- /lib.rs |
322 | fn foo(mut i<|> : u32) -> u32 { | 224 | enum Foo { |
323 | i | 225 | A, |
324 | }"#, | 226 | B<|>, |
325 | "new_name", | 227 | C, |
326 | r#" | 228 | } |
327 | fn foo(mut new_name : u32) -> u32 { | 229 | "#; |
328 | new_name | 230 | |
329 | }"#, | 231 | let refs = get_all_refs(code); |
330 | ); | 232 | assert_eq!(refs.len(), 1); |
331 | } | 233 | } |
332 | 234 | ||
333 | #[test] | 235 | #[test] |
334 | fn test_rename_mod() { | 236 | fn test_find_all_refs_two_modules() { |
335 | let (analysis, position) = analysis_and_position( | 237 | let code = r#" |
336 | " | ||
337 | //- /lib.rs | 238 | //- /lib.rs |
338 | mod bar; | 239 | pub mod foo; |
240 | pub mod bar; | ||
241 | |||
242 | fn f() { | ||
243 | let i = foo::Foo { n: 5 }; | ||
244 | } | ||
245 | |||
246 | //- /foo.rs | ||
247 | use crate::bar; | ||
248 | |||
249 | pub struct Foo { | ||
250 | pub n: u32, | ||
251 | } | ||
252 | |||
253 | fn f() { | ||
254 | let i = bar::Bar { n: 5 }; | ||
255 | } | ||
339 | 256 | ||
340 | //- /bar.rs | 257 | //- /bar.rs |
258 | use crate::foo; | ||
259 | |||
260 | pub struct Bar { | ||
261 | pub n: u32, | ||
262 | } | ||
263 | |||
264 | fn f() { | ||
265 | let i = foo::Foo<|> { n: 5 }; | ||
266 | } | ||
267 | "#; | ||
268 | |||
269 | let (analysis, pos) = analysis_and_position(code); | ||
270 | let refs = analysis.find_all_refs(pos).unwrap().unwrap(); | ||
271 | assert_eq!(refs.len(), 3); | ||
272 | } | ||
273 | |||
274 | // `mod foo;` is not in the results because `foo` is an `ast::Name`. | ||
275 | // So, there are two references: the first one is a definition of the `foo` module, | ||
276 | // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. | ||
277 | #[test] | ||
278 | fn test_find_all_refs_decl_module() { | ||
279 | let code = r#" | ||
280 | //- /lib.rs | ||
341 | mod foo<|>; | 281 | mod foo<|>; |
342 | 282 | ||
343 | //- /bar/foo.rs | 283 | use foo::Foo; |
344 | // emtpy | 284 | |
345 | ", | 285 | fn f() { |
346 | ); | 286 | let i = Foo { n: 5 }; |
347 | let new_name = "foo2"; | 287 | } |
348 | let source_change = analysis.rename(position, new_name).unwrap(); | 288 | |
349 | assert_debug_snapshot!(&source_change, | 289 | //- /foo.rs |
350 | @r###" | 290 | pub struct Foo { |
351 | Some( | 291 | pub n: u32, |
352 | RangeInfo { | 292 | } |
353 | range: [4; 7), | 293 | "#; |
354 | info: SourceChange { | 294 | |
355 | label: "rename", | 295 | let (analysis, pos) = analysis_and_position(code); |
356 | source_file_edits: [ | 296 | let refs = analysis.find_all_refs(pos).unwrap().unwrap(); |
357 | SourceFileEdit { | 297 | assert_eq!(refs.len(), 2); |
358 | file_id: FileId( | ||
359 | 2, | ||
360 | ), | ||
361 | edit: TextEdit { | ||
362 | atoms: [ | ||
363 | AtomTextEdit { | ||
364 | delete: [4; 7), | ||
365 | insert: "foo2", | ||
366 | }, | ||
367 | ], | ||
368 | }, | ||
369 | }, | ||
370 | ], | ||
371 | file_system_edits: [ | ||
372 | MoveFile { | ||
373 | src: FileId( | ||
374 | 3, | ||
375 | ), | ||
376 | dst_source_root: SourceRootId( | ||
377 | 0, | ||
378 | ), | ||
379 | dst_path: "bar/foo2.rs", | ||
380 | }, | ||
381 | ], | ||
382 | cursor_position: None, | ||
383 | }, | ||
384 | }, | ||
385 | ) | ||
386 | "###); | ||
387 | } | 298 | } |
388 | 299 | ||
389 | #[test] | 300 | #[test] |
390 | fn test_rename_mod_in_dir() { | 301 | fn test_find_all_refs_super_mod_vis() { |
391 | let (analysis, position) = analysis_and_position( | 302 | let code = r#" |
392 | " | ||
393 | //- /lib.rs | 303 | //- /lib.rs |
394 | mod fo<|>o; | 304 | mod foo; |
395 | //- /foo/mod.rs | 305 | |
396 | // emtpy | 306 | //- /foo.rs |
397 | ", | 307 | mod some; |
398 | ); | 308 | use some::Foo; |
399 | let new_name = "foo2"; | 309 | |
400 | let source_change = analysis.rename(position, new_name).unwrap(); | 310 | fn f() { |
401 | assert_debug_snapshot!(&source_change, | 311 | let i = Foo { n: 5 }; |
402 | @r###" | 312 | } |
403 | Some( | 313 | |
404 | RangeInfo { | 314 | //- /foo/some.rs |
405 | range: [4; 7), | 315 | pub(super) struct Foo<|> { |
406 | info: SourceChange { | 316 | pub n: u32, |
407 | label: "rename", | 317 | } |
408 | source_file_edits: [ | 318 | "#; |
409 | SourceFileEdit { | 319 | |
410 | file_id: FileId( | 320 | let (analysis, pos) = analysis_and_position(code); |
411 | 1, | 321 | let refs = analysis.find_all_refs(pos).unwrap().unwrap(); |
412 | ), | 322 | assert_eq!(refs.len(), 3); |
413 | edit: TextEdit { | ||
414 | atoms: [ | ||
415 | AtomTextEdit { | ||
416 | delete: [4; 7), | ||
417 | insert: "foo2", | ||
418 | }, | ||
419 | ], | ||
420 | }, | ||
421 | }, | ||
422 | ], | ||
423 | file_system_edits: [ | ||
424 | MoveFile { | ||
425 | src: FileId( | ||
426 | 2, | ||
427 | ), | ||
428 | dst_source_root: SourceRootId( | ||
429 | 0, | ||
430 | ), | ||
431 | dst_path: "foo2/mod.rs", | ||
432 | }, | ||
433 | ], | ||
434 | cursor_position: None, | ||
435 | }, | ||
436 | }, | ||
437 | ) | ||
438 | "### | ||
439 | ); | ||
440 | } | 323 | } |
441 | 324 | ||
442 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 325 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
443 | let (analysis, position) = single_file_with_position(text); | 326 | let (analysis, position) = single_file_with_position(text); |
444 | let source_change = analysis.rename(position, new_name).unwrap(); | 327 | analysis.find_all_refs(position).unwrap().unwrap() |
445 | let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); | ||
446 | let mut file_id: Option<FileId> = None; | ||
447 | if let Some(change) = source_change { | ||
448 | for edit in change.info.source_file_edits { | ||
449 | file_id = Some(edit.file_id); | ||
450 | for atom in edit.edit.as_atoms() { | ||
451 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | ||
452 | } | ||
453 | } | ||
454 | } | ||
455 | let result = | ||
456 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | ||
457 | assert_eq_text!(expected, &*result); | ||
458 | } | 328 | } |
459 | } | 329 | } |