aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/assist_context.rs28
-rw-r--r--crates/assists/src/tests.rs25
-rw-r--r--crates/ide/src/diagnostics.rs12
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs6
-rw-r--r--crates/ide/src/diagnostics/fixes.rs18
-rw-r--r--crates/ide/src/lib.rs2
-rw-r--r--crates/ide/src/references/rename.rs195
-rw-r--r--crates/ide/src/typing.rs3
-rw-r--r--crates/ide_db/src/source_change.rs50
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs2
-rw-r--r--crates/rust-analyzer/src/handlers.rs10
-rw-r--r--crates/rust-analyzer/src/to_proto.rs2
-rw-r--r--crates/ssr/src/lib.rs36
-rw-r--r--crates/ssr/src/matching.rs2
-rw-r--r--crates/ssr/src/tests.rs2
15 files changed, 179 insertions, 214 deletions
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index de4b32573..321fe77f3 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -10,7 +10,7 @@ use ide_db::{
10}; 10};
11use ide_db::{ 11use ide_db::{
12 label::Label, 12 label::Label,
13 source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, 13 source_change::{FileSystemEdit, SourceChange},
14 RootDatabase, 14 RootDatabase,
15}; 15};
16use syntax::{ 16use syntax::{
@@ -180,20 +180,12 @@ impl Assists {
180pub(crate) struct AssistBuilder { 180pub(crate) struct AssistBuilder {
181 edit: TextEditBuilder, 181 edit: TextEditBuilder,
182 file_id: FileId, 182 file_id: FileId,
183 is_snippet: bool, 183 source_change: SourceChange,
184 source_file_edits: SourceFileEdits,
185 file_system_edits: Vec<FileSystemEdit>,
186} 184}
187 185
188impl AssistBuilder { 186impl AssistBuilder {
189 pub(crate) fn new(file_id: FileId) -> AssistBuilder { 187 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
190 AssistBuilder { 188 AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() }
191 edit: TextEdit::builder(),
192 file_id,
193 is_snippet: false,
194 source_file_edits: SourceFileEdits::default(),
195 file_system_edits: Vec::default(),
196 }
197 } 189 }
198 190
199 pub(crate) fn edit_file(&mut self, file_id: FileId) { 191 pub(crate) fn edit_file(&mut self, file_id: FileId) {
@@ -204,7 +196,7 @@ impl AssistBuilder {
204 fn commit(&mut self) { 196 fn commit(&mut self) {
205 let edit = mem::take(&mut self.edit).finish(); 197 let edit = mem::take(&mut self.edit).finish();
206 if !edit.is_empty() { 198 if !edit.is_empty() {
207 self.source_file_edits.insert(self.file_id, edit); 199 self.source_change.insert_source_edit(self.file_id, edit);
208 } 200 }
209 } 201 }
210 202
@@ -223,7 +215,7 @@ impl AssistBuilder {
223 offset: TextSize, 215 offset: TextSize,
224 snippet: impl Into<String>, 216 snippet: impl Into<String>,
225 ) { 217 ) {
226 self.is_snippet = true; 218 self.source_change.is_snippet = true;
227 self.insert(offset, snippet); 219 self.insert(offset, snippet);
228 } 220 }
229 /// Replaces specified `range` of text with a given string. 221 /// Replaces specified `range` of text with a given string.
@@ -237,7 +229,7 @@ impl AssistBuilder {
237 range: TextRange, 229 range: TextRange,
238 snippet: impl Into<String>, 230 snippet: impl Into<String>,
239 ) { 231 ) {
240 self.is_snippet = true; 232 self.source_change.is_snippet = true;
241 self.replace(range, snippet); 233 self.replace(range, snippet);
242 } 234 }
243 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { 235 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
@@ -252,15 +244,11 @@ impl AssistBuilder {
252 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) { 244 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
253 let file_system_edit = 245 let file_system_edit =
254 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() }; 246 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
255 self.file_system_edits.push(file_system_edit); 247 self.source_change.push_file_system_edit(file_system_edit);
256 } 248 }
257 249
258 fn finish(mut self) -> SourceChange { 250 fn finish(mut self) -> SourceChange {
259 self.commit(); 251 self.commit();
260 SourceChange { 252 mem::take(&mut self.source_change)
261 source_file_edits: mem::take(&mut self.source_file_edits),
262 file_system_edits: mem::take(&mut self.file_system_edits),
263 is_snippet: self.is_snippet,
264 }
265 } 253 }
266} 254}
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index d13d6ad31..71431b406 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -80,7 +80,7 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
80 let actual = { 80 let actual = {
81 let source_change = assist.source_change.unwrap(); 81 let source_change = assist.source_change.unwrap();
82 let mut actual = before; 82 let mut actual = before;
83 if let Some(source_file_edit) = source_change.source_file_edits.edits.get(&file_id) { 83 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
84 source_file_edit.apply(&mut actual); 84 source_file_edit.apply(&mut actual);
85 } 85 }
86 actual 86 actual
@@ -120,7 +120,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
120 && source_change.file_system_edits.len() == 0; 120 && source_change.file_system_edits.len() == 0;
121 121
122 let mut buf = String::new(); 122 let mut buf = String::new();
123 for (file_id, edit) in source_change.source_file_edits.edits { 123 for (file_id, edit) in source_change.source_file_edits {
124 let mut text = db.file_text(file_id).as_ref().to_owned(); 124 let mut text = db.file_text(file_id).as_ref().to_owned();
125 edit.apply(&mut text); 125 edit.apply(&mut text);
126 if !skip_header { 126 if !skip_header {
@@ -132,18 +132,15 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
132 buf.push_str(&text); 132 buf.push_str(&text);
133 } 133 }
134 134
135 for file_system_edit in source_change.file_system_edits.clone() { 135 for file_system_edit in source_change.file_system_edits {
136 match file_system_edit { 136 if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
137 FileSystemEdit::CreateFile { dst, initial_contents } => { 137 let sr = db.file_source_root(dst.anchor);
138 let sr = db.file_source_root(dst.anchor); 138 let sr = db.source_root(sr);
139 let sr = db.source_root(sr); 139 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
140 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone(); 140 base.pop();
141 base.pop(); 141 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
142 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]); 142 format_to!(buf, "//- {}\n", created_file_path);
143 format_to!(buf, "//- {}\n", created_file_path); 143 buf.push_str(&initial_contents);
144 buf.push_str(&initial_contents);
145 }
146 _ => (),
147 } 144 }
148 } 145 }
149 146
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index a43188fb0..2e5395b51 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -13,7 +13,7 @@ use hir::{
13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 13 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
14 Semantics, 14 Semantics,
15}; 15};
16use ide_db::{base_db::SourceDatabase, source_change::SourceFileEdits, RootDatabase}; 16use ide_db::{base_db::SourceDatabase, RootDatabase};
17use itertools::Itertools; 17use itertools::Itertools;
18use rustc_hash::FxHashSet; 18use rustc_hash::FxHashSet;
19use syntax::{ 19use syntax::{
@@ -219,7 +219,7 @@ fn check_unnecessary_braces_in_use_statement(
219 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 219 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
220 .with_fix(Some(Fix::new( 220 .with_fix(Some(Fix::new(
221 "Remove unnecessary braces", 221 "Remove unnecessary braces",
222 SourceFileEdits::from_text_edit(file_id, edit).into(), 222 SourceChange::from_text_edit(file_id, edit),
223 use_range, 223 use_range,
224 ))), 224 ))),
225 ); 225 );
@@ -264,10 +264,10 @@ mod tests {
264 .unwrap(); 264 .unwrap();
265 let fix = diagnostic.fix.unwrap(); 265 let fix = diagnostic.fix.unwrap();
266 let actual = { 266 let actual = {
267 let file_id = *fix.source_change.source_file_edits.edits.keys().next().unwrap(); 267 let file_id = *fix.source_change.source_file_edits.keys().next().unwrap();
268 let mut actual = analysis.file_text(file_id).unwrap().to_string(); 268 let mut actual = analysis.file_text(file_id).unwrap().to_string();
269 269
270 for edit in fix.source_change.source_file_edits.edits.values() { 270 for edit in fix.source_change.source_file_edits.values() {
271 edit.apply(&mut actual); 271 edit.apply(&mut actual);
272 } 272 }
273 actual 273 actual
@@ -613,9 +613,7 @@ fn test_fn() {
613 Fix { 613 Fix {
614 label: "Create module", 614 label: "Create module",
615 source_change: SourceChange { 615 source_change: SourceChange {
616 source_file_edits: SourceFileEdits { 616 source_file_edits: {},
617 edits: {},
618 },
619 file_system_edits: [ 617 file_system_edits: [
620 CreateFile { 618 CreateFile {
621 dst: AnchoredPathBuf { 619 dst: AnchoredPathBuf {
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index f4ec51b64..5c89e2170 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -1,7 +1,7 @@
1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both 1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
2//! expressions and patterns. 2//! expressions and patterns.
3 3
4use ide_db::{base_db::FileId, source_change::SourceFileEdits}; 4use ide_db::{base_db::FileId, source_change::SourceChange};
5use syntax::{ast, match_ast, AstNode, SyntaxNode}; 5use syntax::{ast, match_ast, AstNode, SyntaxNode};
6use text_edit::TextEdit; 6use text_edit::TextEdit;
7 7
@@ -49,7 +49,7 @@ fn check_expr_field_shorthand(
49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( 49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix(
50 Some(Fix::new( 50 Some(Fix::new(
51 "Use struct shorthand initialization", 51 "Use struct shorthand initialization",
52 SourceFileEdits::from_text_edit(file_id, edit).into(), 52 SourceChange::from_text_edit(file_id, edit),
53 field_range, 53 field_range,
54 )), 54 )),
55 ), 55 ),
@@ -88,7 +88,7 @@ fn check_pat_field_shorthand(
88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( 88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix(
89 Some(Fix::new( 89 Some(Fix::new(
90 "Use struct field shorthand", 90 "Use struct field shorthand",
91 SourceFileEdits::from_text_edit(file_id, edit).into(), 91 SourceChange::from_text_edit(file_id, edit),
92 field_range, 92 field_range,
93 )), 93 )),
94 )); 94 ));
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index b04964ccd..e4335119b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -10,7 +10,7 @@ use hir::{
10}; 10};
11use ide_db::{ 11use ide_db::{
12 base_db::{AnchoredPathBuf, FileId}, 12 base_db::{AnchoredPathBuf, FileId},
13 source_change::{FileSystemEdit, SourceFileEdits}, 13 source_change::{FileSystemEdit, SourceChange},
14 RootDatabase, 14 RootDatabase,
15}; 15};
16use syntax::{ 16use syntax::{
@@ -88,7 +88,7 @@ impl DiagnosticWithFix for MissingFields {
88 }; 88 };
89 Some(Fix::new( 89 Some(Fix::new(
90 "Fill struct fields", 90 "Fill struct fields",
91 SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(), 91 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
92 sema.original_range(&field_list_parent.syntax()).range, 92 sema.original_range(&field_list_parent.syntax()).range,
93 )) 93 ))
94 } 94 }
@@ -101,8 +101,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
101 let tail_expr_range = tail_expr.syntax().text_range(); 101 let tail_expr_range = tail_expr.syntax().text_range();
102 let replacement = format!("{}({})", self.required, tail_expr.syntax()); 102 let replacement = format!("{}({})", self.required, tail_expr.syntax());
103 let edit = TextEdit::replace(tail_expr_range, replacement); 103 let edit = TextEdit::replace(tail_expr_range, replacement);
104 let source_change = 104 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
105 SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into();
106 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; 105 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
107 Some(Fix::new(name, source_change, tail_expr_range)) 106 Some(Fix::new(name, source_change, tail_expr_range))
108 } 107 }
@@ -122,8 +121,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
122 .text_range(); 121 .text_range();
123 122
124 let edit = TextEdit::delete(semicolon); 123 let edit = TextEdit::delete(semicolon);
125 let source_change = 124 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
126 SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into();
127 125
128 Some(Fix::new("Remove this semicolon", source_change, semicolon)) 126 Some(Fix::new("Remove this semicolon", source_change, semicolon))
129 } 127 }
@@ -204,15 +202,11 @@ fn missing_record_expr_field_fix(
204 new_field = format!(",{}", new_field); 202 new_field = format!(",{}", new_field);
205 } 203 }
206 204
207 let source_change = SourceFileEdits::from_text_edit( 205 let source_change = SourceChange::from_text_edit(
208 def_file_id, 206 def_file_id,
209 TextEdit::insert(last_field_syntax.text_range().end(), new_field), 207 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
210 ); 208 );
211 return Some(Fix::new( 209 return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range()));
212 "Create field",
213 source_change.into(),
214 record_expr_field.syntax().text_range(),
215 ));
216 210
217 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { 211 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
218 match field_def_list { 212 match field_def_list {
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 110920e58..afd552008 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -98,7 +98,7 @@ pub use ide_db::{
98 label::Label, 98 label::Label,
99 line_index::{LineCol, LineIndex}, 99 line_index::{LineCol, LineIndex},
100 search::SearchScope, 100 search::SearchScope,
101 source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, 101 source_change::{FileSystemEdit, SourceChange},
102 symbol_index::Query, 102 symbol_index::Query,
103 RootDatabase, 103 RootDatabase,
104}; 104};
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index cd9c7c7e5..039efb26f 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -21,7 +21,7 @@ use text_edit::TextEdit;
21 21
22use crate::{ 22use crate::{
23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, 23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
24 SourceFileEdits, TextRange, TextSize, 24 TextRange, TextSize,
25}; 25};
26 26
27type RenameResult<T> = Result<T, RenameError>; 27type RenameResult<T> = Result<T, RenameError>;
@@ -249,8 +249,8 @@ fn rename_mod(
249 if IdentifierKind::Ident != check_identifier(new_name)? { 249 if IdentifierKind::Ident != check_identifier(new_name)? {
250 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 250 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
251 } 251 }
252 let mut source_file_edits = SourceFileEdits::default(); 252
253 let mut file_system_edits = Vec::new(); 253 let mut source_change = SourceChange::default();
254 254
255 let src = module.definition_source(sema.db); 255 let src = module.definition_source(sema.db);
256 let file_id = src.file_id.original_file(sema.db); 256 let file_id = src.file_id.original_file(sema.db);
@@ -264,7 +264,7 @@ fn rename_mod(
264 }; 264 };
265 let dst = AnchoredPathBuf { anchor: file_id, path }; 265 let dst = AnchoredPathBuf { anchor: file_id, path };
266 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 266 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
267 file_system_edits.push(move_file); 267 source_change.push_file_system_edit(move_file);
268 } 268 }
269 ModuleSource::Module(..) => {} 269 ModuleSource::Module(..) => {}
270 } 270 }
@@ -272,17 +272,19 @@ fn rename_mod(
272 if let Some(src) = module.declaration_source(sema.db) { 272 if let Some(src) = module.declaration_source(sema.db) {
273 let file_id = src.file_id.original_file(sema.db); 273 let file_id = src.file_id.original_file(sema.db);
274 let name = src.value.name().unwrap(); 274 let name = src.value.name().unwrap();
275 source_file_edits 275 source_change.insert_source_edit(
276 .insert(file_id, TextEdit::replace(name.syntax().text_range(), new_name.into())); 276 file_id,
277 TextEdit::replace(name.syntax().text_range(), new_name.into()),
278 );
277 } 279 }
278 280
279 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 281 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
280 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 282 let ref_edits = refs.references().iter().map(|(&file_id, references)| {
281 source_edit_from_references(sema, file_id, references, new_name) 283 source_edit_from_references(sema, file_id, references, new_name)
282 }); 284 });
283 source_file_edits.extend(ref_edits); 285 source_change.extend(ref_edits);
284 286
285 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 287 Ok(RangeInfo::new(range, source_change))
286} 288}
287 289
288fn rename_to_self( 290fn rename_to_self(
@@ -331,13 +333,16 @@ fn rename_to_self(
331 333
332 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 334 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
333 335
334 let mut edits = SourceFileEdits::default(); 336 let mut source_change = SourceChange::default();
335 edits.extend(refs.references().iter().map(|(&file_id, references)| { 337 source_change.extend(refs.references().iter().map(|(&file_id, references)| {
336 source_edit_from_references(sema, file_id, references, "self") 338 source_edit_from_references(sema, file_id, references, "self")
337 })); 339 }));
338 edits.insert(position.file_id, TextEdit::replace(param_range, String::from(self_param))); 340 source_change.insert_source_edit(
341 position.file_id,
342 TextEdit::replace(param_range, String::from(self_param)),
343 );
339 344
340 Ok(RangeInfo::new(range, edits.into())) 345 Ok(RangeInfo::new(range, source_change))
341} 346}
342 347
343fn text_edit_from_self_param( 348fn text_edit_from_self_param(
@@ -391,7 +396,7 @@ fn rename_self_to_param(
391 .ok_or_else(|| format_err!("No surrounding method declaration found"))?; 396 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
392 let search_range = fn_def.syntax().text_range(); 397 let search_range = fn_def.syntax().text_range();
393 398
394 let mut edits = SourceFileEdits::default(); 399 let mut source_change = SourceChange::default();
395 400
396 for (idx, _) in text.match_indices("self") { 401 for (idx, _) in text.match_indices("self") {
397 let offset: TextSize = idx.try_into().unwrap(); 402 let offset: TextSize = idx.try_into().unwrap();
@@ -405,18 +410,18 @@ fn rename_self_to_param(
405 } else { 410 } else {
406 TextEdit::replace(usage.text_range(), String::from(new_name)) 411 TextEdit::replace(usage.text_range(), String::from(new_name))
407 }; 412 };
408 edits.insert(position.file_id, edit); 413 source_change.insert_source_edit(position.file_id, edit);
409 } 414 }
410 } 415 }
411 416
412 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { 417 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
413 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 418 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
414 } 419 }
415 420
416 let range = ast::SelfParam::cast(self_token.parent()) 421 let range = ast::SelfParam::cast(self_token.parent())
417 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 422 .map_or(self_token.text_range(), |p| p.syntax().text_range());
418 423
419 Ok(RangeInfo::new(range, edits.into())) 424 Ok(RangeInfo::new(range, source_change))
420} 425}
421 426
422fn rename_reference( 427fn rename_reference(
@@ -453,12 +458,12 @@ fn rename_reference(
453 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 458 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
454 } 459 }
455 460
456 let mut edits = SourceFileEdits::default(); 461 let mut source_change = SourceChange::default();
457 edits.extend(refs.into_iter().map(|(file_id, references)| { 462 source_change.extend(refs.into_iter().map(|(file_id, references)| {
458 source_edit_from_references(sema, file_id, &references, new_name) 463 source_edit_from_references(sema, file_id, &references, new_name)
459 })); 464 }));
460 465
461 Ok(RangeInfo::new(range, edits.into())) 466 Ok(RangeInfo::new(range, source_change))
462} 467}
463 468
464#[cfg(test)] 469#[cfg(test)]
@@ -480,7 +485,7 @@ mod tests {
480 Ok(source_change) => { 485 Ok(source_change) => {
481 let mut text_edit_builder = TextEdit::builder(); 486 let mut text_edit_builder = TextEdit::builder();
482 let mut file_id: Option<FileId> = None; 487 let mut file_id: Option<FileId> = None;
483 for edit in source_change.info.source_file_edits.edits { 488 for edit in source_change.info.source_file_edits {
484 file_id = Some(edit.0); 489 file_id = Some(edit.0);
485 for indel in edit.1.into_iter() { 490 for indel in edit.1.into_iter() {
486 text_edit_builder.replace(indel.delete, indel.insert); 491 text_edit_builder.replace(indel.delete, indel.insert);
@@ -882,18 +887,16 @@ mod foo$0;
882 RangeInfo { 887 RangeInfo {
883 range: 4..7, 888 range: 4..7,
884 info: SourceChange { 889 info: SourceChange {
885 source_file_edits: SourceFileEdits { 890 source_file_edits: {
886 edits: { 891 FileId(
887 FileId( 892 1,
888 1, 893 ): TextEdit {
889 ): TextEdit { 894 indels: [
890 indels: [ 895 Indel {
891 Indel { 896 insert: "foo2",
892 insert: "foo2", 897 delete: 4..7,
893 delete: 4..7, 898 },
894 }, 899 ],
895 ],
896 },
897 }, 900 },
898 }, 901 },
899 file_system_edits: [ 902 file_system_edits: [
@@ -936,28 +939,26 @@ use crate::foo$0::FooContent;
936 RangeInfo { 939 RangeInfo {
937 range: 11..14, 940 range: 11..14,
938 info: SourceChange { 941 info: SourceChange {
939 source_file_edits: SourceFileEdits { 942 source_file_edits: {
940 edits: { 943 FileId(
941 FileId( 944 0,
942 0, 945 ): TextEdit {
943 ): TextEdit { 946 indels: [
944 indels: [ 947 Indel {
945 Indel { 948 insert: "quux",
946 insert: "quux", 949 delete: 8..11,
947 delete: 8..11, 950 },
948 }, 951 ],
949 ], 952 },
950 }, 953 FileId(
951 FileId( 954 2,
952 2, 955 ): TextEdit {
953 ): TextEdit { 956 indels: [
954 indels: [ 957 Indel {
955 Indel { 958 insert: "quux",
956 insert: "quux", 959 delete: 11..14,
957 delete: 11..14, 960 },
958 }, 961 ],
959 ],
960 },
961 }, 962 },
962 }, 963 },
963 file_system_edits: [ 964 file_system_edits: [
@@ -994,18 +995,16 @@ mod fo$0o;
994 RangeInfo { 995 RangeInfo {
995 range: 4..7, 996 range: 4..7,
996 info: SourceChange { 997 info: SourceChange {
997 source_file_edits: SourceFileEdits { 998 source_file_edits: {
998 edits: { 999 FileId(
999 FileId( 1000 0,
1000 0, 1001 ): TextEdit {
1001 ): TextEdit { 1002 indels: [
1002 indels: [ 1003 Indel {
1003 Indel { 1004 insert: "foo2",
1004 insert: "foo2", 1005 delete: 4..7,
1005 delete: 4..7, 1006 },
1006 }, 1007 ],
1007 ],
1008 },
1009 }, 1008 },
1010 }, 1009 },
1011 file_system_edits: [ 1010 file_system_edits: [
@@ -1043,18 +1042,16 @@ mod outer { mod fo$0o; }
1043 RangeInfo { 1042 RangeInfo {
1044 range: 16..19, 1043 range: 16..19,
1045 info: SourceChange { 1044 info: SourceChange {
1046 source_file_edits: SourceFileEdits { 1045 source_file_edits: {
1047 edits: { 1046 FileId(
1048 FileId( 1047 0,
1049 0, 1048 ): TextEdit {
1050 ): TextEdit { 1049 indels: [
1051 indels: [ 1050 Indel {
1052 Indel { 1051 insert: "bar",
1053 insert: "bar", 1052 delete: 16..19,
1054 delete: 16..19, 1053 },
1055 }, 1054 ],
1056 ],
1057 },
1058 }, 1055 },
1059 }, 1056 },
1060 file_system_edits: [ 1057 file_system_edits: [
@@ -1115,28 +1112,26 @@ pub mod foo$0;
1115 RangeInfo { 1112 RangeInfo {
1116 range: 8..11, 1113 range: 8..11,
1117 info: SourceChange { 1114 info: SourceChange {
1118 source_file_edits: SourceFileEdits { 1115 source_file_edits: {
1119 edits: { 1116 FileId(
1120 FileId( 1117 0,
1121 0, 1118 ): TextEdit {
1122 ): TextEdit { 1119 indels: [
1123 indels: [ 1120 Indel {
1124 Indel { 1121 insert: "foo2",
1125 insert: "foo2", 1122 delete: 27..30,
1126 delete: 27..30, 1123 },
1127 }, 1124 ],
1128 ], 1125 },
1129 }, 1126 FileId(
1130 FileId( 1127 1,
1131 1, 1128 ): TextEdit {
1132 ): TextEdit { 1129 indels: [
1133 indels: [ 1130 Indel {
1134 Indel { 1131 insert: "foo2",
1135 insert: "foo2", 1132 delete: 8..11,
1136 delete: 8..11, 1133 },
1137 }, 1134 ],
1138 ],
1139 },
1140 }, 1135 },
1141 }, 1136 },
1142 file_system_edits: [ 1137 file_system_edits: [
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index b3fc32645..e3c3aebac 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -17,7 +17,6 @@ mod on_enter;
17 17
18use ide_db::{ 18use ide_db::{
19 base_db::{FilePosition, SourceDatabase}, 19 base_db::{FilePosition, SourceDatabase},
20 source_change::SourceFileEdits,
21 RootDatabase, 20 RootDatabase,
22}; 21};
23use syntax::{ 22use syntax::{
@@ -59,7 +58,7 @@ pub(crate) fn on_char_typed(
59 let file = &db.parse(position.file_id).tree(); 58 let file = &db.parse(position.file_id).tree();
60 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 59 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
61 let edit = on_char_typed_inner(file, position.offset, char_typed)?; 60 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
62 Some(SourceFileEdits::from_text_edit(position.file_id, edit).into()) 61 Some(SourceChange::from_text_edit(position.file_id, edit))
63} 62}
64 63
65fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 64fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs
index 516d7859a..b1f87731b 100644
--- a/crates/ide_db/src/source_change.rs
+++ b/crates/ide_db/src/source_change.rs
@@ -10,11 +10,12 @@ use std::{
10 10
11use base_db::{AnchoredPathBuf, FileId}; 11use base_db::{AnchoredPathBuf, FileId};
12use rustc_hash::FxHashMap; 12use rustc_hash::FxHashMap;
13use stdx::assert_never;
13use text_edit::TextEdit; 14use text_edit::TextEdit;
14 15
15#[derive(Default, Debug, Clone)] 16#[derive(Default, Debug, Clone)]
16pub struct SourceChange { 17pub struct SourceChange {
17 pub source_file_edits: SourceFileEdits, 18 pub source_file_edits: FxHashMap<FileId, TextEdit>,
18 pub file_system_edits: Vec<FileSystemEdit>, 19 pub file_system_edits: Vec<FileSystemEdit>,
19 pub is_snippet: bool, 20 pub is_snippet: bool,
20} 21}
@@ -23,51 +24,50 @@ impl SourceChange {
23 /// Creates a new SourceChange with the given label 24 /// Creates a new SourceChange with the given label
24 /// from the edits. 25 /// from the edits.
25 pub fn from_edits( 26 pub fn from_edits(
26 source_file_edits: SourceFileEdits, 27 source_file_edits: FxHashMap<FileId, TextEdit>,
27 file_system_edits: Vec<FileSystemEdit>, 28 file_system_edits: Vec<FileSystemEdit>,
28 ) -> Self { 29 ) -> Self {
29 SourceChange { source_file_edits, file_system_edits, is_snippet: false } 30 SourceChange { source_file_edits, file_system_edits, is_snippet: false }
30 } 31 }
31}
32
33#[derive(Default, Debug, Clone)]
34pub struct SourceFileEdits {
35 pub edits: FxHashMap<FileId, TextEdit>,
36}
37 32
38impl SourceFileEdits {
39 pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self { 33 pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
40 SourceFileEdits { edits: FxHashMap::from_iter(iter::once((file_id, edit))) } 34 SourceChange {
41 } 35 source_file_edits: FxHashMap::from_iter(iter::once((file_id, edit))),
42 36 ..Default::default()
43 pub fn len(&self) -> usize { 37 }
44 self.edits.len()
45 }
46
47 pub fn is_empty(&self) -> bool {
48 self.edits.is_empty()
49 } 38 }
50 39
51 pub fn insert(&mut self, file_id: FileId, edit: TextEdit) { 40 pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) {
52 match self.edits.entry(file_id) { 41 match self.source_file_edits.entry(file_id) {
53 Entry::Occupied(mut entry) => { 42 Entry::Occupied(mut entry) => {
54 entry.get_mut().union(edit).expect("overlapping edits for same file"); 43 assert_never!(
44 entry.get_mut().union(edit).is_err(),
45 "overlapping edits for same file"
46 );
55 } 47 }
56 Entry::Vacant(entry) => { 48 Entry::Vacant(entry) => {
57 entry.insert(edit); 49 entry.insert(edit);
58 } 50 }
59 } 51 }
60 } 52 }
53
54 pub fn push_file_system_edit(&mut self, edit: FileSystemEdit) {
55 self.file_system_edits.push(edit);
56 }
57
58 pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> {
59 self.source_file_edits.get(&file_id)
60 }
61} 61}
62 62
63impl Extend<(FileId, TextEdit)> for SourceFileEdits { 63impl Extend<(FileId, TextEdit)> for SourceChange {
64 fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) { 64 fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) {
65 iter.into_iter().for_each(|(file_id, edit)| self.insert(file_id, edit)); 65 iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit));
66 } 66 }
67} 67}
68 68
69impl From<SourceFileEdits> for SourceChange { 69impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
70 fn from(source_file_edits: SourceFileEdits) -> SourceChange { 70 fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange {
71 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } 71 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
72 } 72 }
73} 73}
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 100331c37..bbb550ec9 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -12,7 +12,7 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
12 match_finder.add_rule(rule)?; 12 match_finder.add_rule(rule)?;
13 } 13 }
14 let edits = match_finder.edits(); 14 let edits = match_finder.edits();
15 for (file_id, edit) in edits.edits { 15 for (file_id, edit) in edits {
16 if let Some(path) = vfs.file_path(file_id).as_path() { 16 if let Some(path) = vfs.file_path(file_id).as_path() {
17 let mut contents = db.file_text(file_id).to_string(); 17 let mut contents = db.file_text(file_id).to_string();
18 edit.apply(&mut contents); 18 edit.apply(&mut contents);
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 839466e4f..1a4e0dd32 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -266,7 +266,7 @@ pub(crate) fn handle_on_type_formatting(
266 }; 266 };
267 267
268 // This should be a single-file edit 268 // This should be a single-file edit
269 let (_, edit) = edit.source_file_edits.edits.into_iter().next().unwrap(); 269 let (_, edit) = edit.source_file_edits.into_iter().next().unwrap();
270 270
271 let change = to_proto::text_edit_vec(&line_index, line_endings, edit); 271 let change = to_proto::text_edit_vec(&line_index, line_endings, edit);
272 Ok(Some(change)) 272 Ok(Some(change))
@@ -464,12 +464,10 @@ pub(crate) fn handle_will_rename_files(
464 464
465 // Drop file system edits since we're just renaming things on the same level 465 // Drop file system edits since we're just renaming things on the same level
466 let mut source_changes = source_changes.into_iter(); 466 let mut source_changes = source_changes.into_iter();
467 let mut source_file_edits = 467 let mut source_change = source_changes.next().unwrap_or_default();
468 source_changes.next().map_or_else(Default::default, |it| it.source_file_edits); 468 source_change.file_system_edits.clear();
469 // no collect here because we want to merge text edits on same file ids 469 // no collect here because we want to merge text edits on same file ids
470 source_file_edits.extend(source_changes.map(|it| it.source_file_edits.edits).flatten()); 470 source_change.extend(source_changes.map(|it| it.source_file_edits).flatten());
471 let source_change = SourceChange::from_edits(source_file_edits, Vec::new());
472
473 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; 471 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
474 Ok(Some(workspace_edit)) 472 Ok(Some(workspace_edit))
475} 473}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 34f510e73..dc67d19a7 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -698,7 +698,7 @@ pub(crate) fn snippet_workspace_edit(
698 let ops = snippet_text_document_ops(snap, op); 698 let ops = snippet_text_document_ops(snap, op);
699 document_changes.extend_from_slice(&ops); 699 document_changes.extend_from_slice(&ops);
700 } 700 }
701 for (file_id, edit) in source_change.source_file_edits.edits { 701 for (file_id, edit) in source_change.source_file_edits {
702 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; 702 let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?;
703 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); 703 document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
704 } 704 }
diff --git a/crates/ssr/src/lib.rs b/crates/ssr/src/lib.rs
index d99ebefb1..a97fc8bca 100644
--- a/crates/ssr/src/lib.rs
+++ b/crates/ssr/src/lib.rs
@@ -74,13 +74,11 @@ pub use crate::errors::SsrError;
74pub use crate::matching::Match; 74pub use crate::matching::Match;
75use crate::matching::MatchFailureReason; 75use crate::matching::MatchFailureReason;
76use hir::Semantics; 76use hir::Semantics;
77use ide_db::{ 77use ide_db::base_db::{FileId, FilePosition, FileRange};
78 base_db::{FileId, FilePosition, FileRange},
79 source_change::SourceFileEdits,
80};
81use resolving::ResolvedRule; 78use resolving::ResolvedRule;
82use rustc_hash::FxHashMap; 79use rustc_hash::FxHashMap;
83use syntax::{ast, AstNode, SyntaxNode, TextRange}; 80use syntax::{ast, AstNode, SyntaxNode, TextRange};
81use text_edit::TextEdit;
84 82
85// A structured search replace rule. Create by calling `parse` on a str. 83// A structured search replace rule. Create by calling `parse` on a str.
86#[derive(Debug)] 84#[derive(Debug)]
@@ -161,7 +159,7 @@ impl<'db> MatchFinder<'db> {
161 } 159 }
162 160
163 /// Finds matches for all added rules and returns edits for all found matches. 161 /// Finds matches for all added rules and returns edits for all found matches.
164 pub fn edits(&self) -> SourceFileEdits { 162 pub fn edits(&self) -> FxHashMap<FileId, TextEdit> {
165 use ide_db::base_db::SourceDatabaseExt; 163 use ide_db::base_db::SourceDatabaseExt;
166 let mut matches_by_file = FxHashMap::default(); 164 let mut matches_by_file = FxHashMap::default();
167 for m in self.matches().matches { 165 for m in self.matches().matches {
@@ -171,21 +169,19 @@ impl<'db> MatchFinder<'db> {
171 .matches 169 .matches
172 .push(m); 170 .push(m);
173 } 171 }
174 SourceFileEdits { 172 matches_by_file
175 edits: matches_by_file 173 .into_iter()
176 .into_iter() 174 .map(|(file_id, matches)| {
177 .map(|(file_id, matches)| { 175 (
178 ( 176 file_id,
179 file_id, 177 replacing::matches_to_edit(
180 replacing::matches_to_edit( 178 &matches,
181 &matches, 179 &self.sema.db.file_text(file_id),
182 &self.sema.db.file_text(file_id), 180 &self.rules,
183 &self.rules, 181 ),
184 ), 182 )
185 ) 183 })
186 }) 184 .collect()
187 .collect(),
188 }
189 } 185 }
190 186
191 /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you 187 /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index 5888bf8f8..42d313f91 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -810,7 +810,7 @@ mod tests {
810 810
811 let edits = match_finder.edits(); 811 let edits = match_finder.edits();
812 assert_eq!(edits.len(), 1); 812 assert_eq!(edits.len(), 1);
813 let edit = &edits.edits[&position.file_id]; 813 let edit = &edits[&position.file_id];
814 let mut after = input.to_string(); 814 let mut after = input.to_string();
815 edit.apply(&mut after); 815 edit.apply(&mut after);
816 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }"); 816 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs
index 8ba783526..a3ea44f23 100644
--- a/crates/ssr/src/tests.rs
+++ b/crates/ssr/src/tests.rs
@@ -106,7 +106,7 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
106 // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters 106 // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
107 // stuff. 107 // stuff.
108 let mut actual = db.file_text(position.file_id).to_string(); 108 let mut actual = db.file_text(position.file_id).to_string();
109 edits.edits[&position.file_id].apply(&mut actual); 109 edits[&position.file_id].apply(&mut actual);
110 expected.assert_eq(&actual); 110 expected.assert_eq(&actual);
111} 111}
112 112