From f51457a643b768794092f73add6dda4aecd400a1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 14 Jan 2021 18:35:22 +0100 Subject: Group file source edits by FileId --- crates/assists/src/assist_context.rs | 16 +-- crates/assists/src/tests.rs | 19 ++-- crates/ide/src/diagnostics.rs | 19 ++-- crates/ide/src/diagnostics/field_shorthand.rs | 7 +- crates/ide/src/diagnostics/fixes.rs | 18 ++-- crates/ide/src/lib.rs | 4 +- crates/ide/src/references/rename.rs | 138 +++++++++++--------------- crates/ide/src/typing.rs | 9 +- crates/ide_db/src/source_change.rs | 54 +++++++--- crates/rust-analyzer/src/cli/ssr.rs | 8 +- crates/rust-analyzer/src/handlers.rs | 14 ++- crates/rust-analyzer/src/to_proto.rs | 19 ++-- crates/ssr/src/lib.rs | 28 ++++-- crates/ssr/src/matching.rs | 4 +- crates/ssr/src/tests.rs | 3 +- 15 files changed, 184 insertions(+), 176 deletions(-) diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs index 91cc63427..de4b32573 100644 --- a/crates/assists/src/assist_context.rs +++ b/crates/assists/src/assist_context.rs @@ -10,7 +10,7 @@ use ide_db::{ }; use ide_db::{ label::Label, - source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, + source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, RootDatabase, }; use syntax::{ @@ -181,7 +181,7 @@ pub(crate) struct AssistBuilder { edit: TextEditBuilder, file_id: FileId, is_snippet: bool, - source_file_edits: Vec, + source_file_edits: SourceFileEdits, file_system_edits: Vec, } @@ -191,7 +191,7 @@ impl AssistBuilder { edit: TextEdit::builder(), file_id, is_snippet: false, - source_file_edits: Vec::default(), + source_file_edits: SourceFileEdits::default(), file_system_edits: Vec::default(), } } @@ -204,15 +204,7 @@ impl AssistBuilder { fn commit(&mut self) { let edit = mem::take(&mut self.edit).finish(); if !edit.is_empty() { - match self.source_file_edits.binary_search_by_key(&self.file_id, |edit| edit.file_id) { - Ok(idx) => self.source_file_edits[idx] - .edit - .union(edit) - .expect("overlapping edits for same file"), - Err(idx) => self - .source_file_edits - .insert(idx, SourceFileEdit { file_id: self.file_id, edit }), - } + self.source_file_edits.insert(self.file_id, edit); } } diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index fef29a0b8..d13d6ad31 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs @@ -80,10 +80,8 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { let actual = { let source_change = assist.source_change.unwrap(); let mut actual = before; - for source_file_edit in source_change.source_file_edits { - if source_file_edit.file_id == file_id { - source_file_edit.edit.apply(&mut actual) - } + if let Some(source_file_edit) = source_change.source_file_edits.edits.get(&file_id) { + source_file_edit.apply(&mut actual); } actual }; @@ -116,20 +114,19 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: match (assist, expected) { (Some(assist), ExpectedResult::After(after)) => { - let mut source_change = assist.source_change.unwrap(); + let source_change = assist.source_change.unwrap(); assert!(!source_change.source_file_edits.is_empty()); let skip_header = source_change.source_file_edits.len() == 1 && source_change.file_system_edits.len() == 0; - source_change.source_file_edits.sort_by_key(|it| it.file_id); let mut buf = String::new(); - for source_file_edit in source_change.source_file_edits { - let mut text = db.file_text(source_file_edit.file_id).as_ref().to_owned(); - source_file_edit.edit.apply(&mut text); + for (file_id, edit) in source_change.source_file_edits.edits { + let mut text = db.file_text(file_id).as_ref().to_owned(); + edit.apply(&mut text); if !skip_header { - let sr = db.file_source_root(source_file_edit.file_id); + let sr = db.file_source_root(file_id); let sr = db.source_root(sr); - let path = sr.path_for_file(&source_file_edit.file_id).unwrap(); + let path = sr.path_for_file(&file_id).unwrap(); format_to!(buf, "//- {}\n", path) } buf.push_str(&text); diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 055c0a79c..a43188fb0 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -13,8 +13,7 @@ use hir::{ diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, Semantics, }; -use ide_db::base_db::SourceDatabase; -use ide_db::RootDatabase; +use ide_db::{base_db::SourceDatabase, source_change::SourceFileEdits, RootDatabase}; use itertools::Itertools; use rustc_hash::FxHashSet; use syntax::{ @@ -23,7 +22,7 @@ use syntax::{ }; use text_edit::TextEdit; -use crate::{FileId, Label, SourceChange, SourceFileEdit}; +use crate::{FileId, Label, SourceChange}; use self::fixes::DiagnosticWithFix; @@ -220,7 +219,7 @@ fn check_unnecessary_braces_in_use_statement( Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) .with_fix(Some(Fix::new( "Remove unnecessary braces", - SourceFileEdit { file_id, edit }.into(), + SourceFileEdits::from_text_edit(file_id, edit).into(), use_range, ))), ); @@ -265,13 +264,11 @@ mod tests { .unwrap(); let fix = diagnostic.fix.unwrap(); let actual = { - let file_id = fix.source_change.source_file_edits.first().unwrap().file_id; + let file_id = *fix.source_change.source_file_edits.edits.keys().next().unwrap(); let mut actual = analysis.file_text(file_id).unwrap().to_string(); - // Go from the last one to the first one, so that ranges won't be affected by previous edits. - // FIXME: https://github.com/rust-analyzer/rust-analyzer/issues/4901#issuecomment-644675309 - for edit in fix.source_change.source_file_edits.iter().rev() { - edit.edit.apply(&mut actual); + for edit in fix.source_change.source_file_edits.edits.values() { + edit.apply(&mut actual); } actual }; @@ -616,7 +613,9 @@ fn test_fn() { Fix { label: "Create module", source_change: SourceChange { - source_file_edits: [], + source_file_edits: SourceFileEdits { + edits: {}, + }, file_system_edits: [ CreateFile { dst: AnchoredPathBuf { diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs index 16c6ea827..f4ec51b64 100644 --- a/crates/ide/src/diagnostics/field_shorthand.rs +++ b/crates/ide/src/diagnostics/field_shorthand.rs @@ -1,8 +1,7 @@ //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both //! expressions and patterns. -use ide_db::base_db::FileId; -use ide_db::source_change::SourceFileEdit; +use ide_db::{base_db::FileId, source_change::SourceFileEdits}; use syntax::{ast, match_ast, AstNode, SyntaxNode}; use text_edit::TextEdit; @@ -50,7 +49,7 @@ fn check_expr_field_shorthand( Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( Some(Fix::new( "Use struct shorthand initialization", - SourceFileEdit { file_id, edit }.into(), + SourceFileEdits::from_text_edit(file_id, edit).into(), field_range, )), ), @@ -89,7 +88,7 @@ fn check_pat_field_shorthand( acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( Some(Fix::new( "Use struct field shorthand", - SourceFileEdit { file_id, edit }.into(), + SourceFileEdits::from_text_edit(file_id, edit).into(), field_range, )), )); diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index d7ad88ed5..b04964ccd 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs @@ -8,9 +8,9 @@ use hir::{ }, HasSource, HirDisplay, InFile, Semantics, VariantDef, }; -use ide_db::base_db::{AnchoredPathBuf, FileId}; use ide_db::{ - source_change::{FileSystemEdit, SourceFileEdit}, + base_db::{AnchoredPathBuf, FileId}, + source_change::{FileSystemEdit, SourceFileEdits}, RootDatabase, }; use syntax::{ @@ -88,7 +88,7 @@ impl DiagnosticWithFix for MissingFields { }; Some(Fix::new( "Fill struct fields", - SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), + SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(), sema.original_range(&field_list_parent.syntax()).range, )) } @@ -102,7 +102,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { let replacement = format!("{}({})", self.required, tail_expr.syntax()); let edit = TextEdit::replace(tail_expr_range, replacement); let source_change = - SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); + SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(); let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; Some(Fix::new(name, source_change, tail_expr_range)) } @@ -123,7 +123,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon { let edit = TextEdit::delete(semicolon); let source_change = - SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); + SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(); Some(Fix::new("Remove this semicolon", source_change, semicolon)) } @@ -204,10 +204,10 @@ fn missing_record_expr_field_fix( new_field = format!(",{}", new_field); } - let source_change = SourceFileEdit { - file_id: def_file_id, - edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), - }; + let source_change = SourceFileEdits::from_text_edit( + def_file_id, + TextEdit::insert(last_field_syntax.text_range().end(), new_field), + ); return Some(Fix::new( "Create field", source_change.into(), diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1e03832ec..110920e58 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -98,7 +98,7 @@ pub use ide_db::{ label::Label, line_index::{LineCol, LineIndex}, search::SearchScope, - source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, + source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, symbol_index::Query, RootDatabase, }; @@ -553,7 +553,7 @@ impl Analysis { let rule: ssr::SsrRule = query.parse()?; let mut match_finder = ssr::MatchFinder::in_context(db, resolve_context, selections); match_finder.add_rule(rule)?; - let edits = if parse_only { Vec::new() } else { match_finder.edits() }; + let edits = if parse_only { Default::default() } else { match_finder.edits() }; Ok(SourceChange::from(edits)) }) } diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index c3ae568c2..cd9c7c7e5 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs @@ -21,7 +21,7 @@ use text_edit::TextEdit; use crate::{ FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, - SourceFileEdit, TextRange, TextSize, + SourceFileEdits, TextRange, TextSize, }; type RenameResult = Result; @@ -58,7 +58,7 @@ pub(crate) fn prepare_rename( rename_self_to_param(&sema, position, self_token, "dummy") } else { let RangeInfo { range, .. } = find_all_refs(&sema, position)?; - Ok(RangeInfo::new(range, SourceChange::from(vec![]))) + Ok(RangeInfo::new(range, SourceChange::default())) } .map(|info| RangeInfo::new(info.range, ())) } @@ -176,7 +176,7 @@ fn source_edit_from_references( file_id: FileId, references: &[FileReference], new_name: &str, -) -> SourceFileEdit { +) -> (FileId, TextEdit) { let mut edit = TextEdit::builder(); for reference in references { let mut replacement_text = String::new(); @@ -209,8 +209,7 @@ fn source_edit_from_references( }; edit.replace(range, replacement_text); } - - SourceFileEdit { file_id, edit: edit.finish() } + (file_id, edit.finish()) } fn edit_text_range_for_record_field_expr_or_pat( @@ -250,7 +249,7 @@ fn rename_mod( if IdentifierKind::Ident != check_identifier(new_name)? { bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); } - let mut source_file_edits = Vec::new(); + let mut source_file_edits = SourceFileEdits::default(); let mut file_system_edits = Vec::new(); let src = module.definition_source(sema.db); @@ -273,11 +272,8 @@ fn rename_mod( if let Some(src) = module.declaration_source(sema.db) { let file_id = src.file_id.original_file(sema.db); let name = src.value.name().unwrap(); - let edit = SourceFileEdit { - file_id, - edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), - }; - source_file_edits.push(edit); + source_file_edits + .insert(file_id, TextEdit::replace(name.syntax().text_range(), new_name.into())); } let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; @@ -335,20 +331,13 @@ fn rename_to_self( let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; - let mut edits = refs - .references() - .iter() - .map(|(&file_id, references)| { - source_edit_from_references(sema, file_id, references, "self") - }) - .collect::>(); + let mut edits = SourceFileEdits::default(); + edits.extend(refs.references().iter().map(|(&file_id, references)| { + source_edit_from_references(sema, file_id, references, "self") + })); + edits.insert(position.file_id, TextEdit::replace(param_range, String::from(self_param))); - edits.push(SourceFileEdit { - file_id: position.file_id, - edit: TextEdit::replace(param_range, String::from(self_param)), - }); - - Ok(RangeInfo::new(range, SourceChange::from(edits))) + Ok(RangeInfo::new(range, edits.into())) } fn text_edit_from_self_param( @@ -402,7 +391,7 @@ fn rename_self_to_param( .ok_or_else(|| format_err!("No surrounding method declaration found"))?; let search_range = fn_def.syntax().text_range(); - let mut edits: Vec = vec![]; + let mut edits = SourceFileEdits::default(); for (idx, _) in text.match_indices("self") { let offset: TextSize = idx.try_into().unwrap(); @@ -416,7 +405,7 @@ fn rename_self_to_param( } else { TextEdit::replace(usage.text_range(), String::from(new_name)) }; - edits.push(SourceFileEdit { file_id: position.file_id, edit }); + edits.insert(position.file_id, edit); } } @@ -427,7 +416,7 @@ fn rename_self_to_param( let range = ast::SelfParam::cast(self_token.parent()) .map_or(self_token.text_range(), |p| p.syntax().text_range()); - Ok(RangeInfo::new(range, SourceChange::from(edits))) + Ok(RangeInfo::new(range, edits.into())) } fn rename_reference( @@ -464,14 +453,12 @@ fn rename_reference( (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), } - let edit = refs - .into_iter() - .map(|(file_id, references)| { - source_edit_from_references(sema, file_id, &references, new_name) - }) - .collect::>(); + let mut edits = SourceFileEdits::default(); + edits.extend(refs.into_iter().map(|(file_id, references)| { + source_edit_from_references(sema, file_id, &references, new_name) + })); - Ok(RangeInfo::new(range, SourceChange::from(edit))) + Ok(RangeInfo::new(range, edits.into())) } #[cfg(test)] @@ -493,9 +480,9 @@ mod tests { Ok(source_change) => { let mut text_edit_builder = TextEdit::builder(); let mut file_id: Option = None; - for edit in source_change.info.source_file_edits { - file_id = Some(edit.file_id); - for indel in edit.edit.into_iter() { + for edit in source_change.info.source_file_edits.edits { + file_id = Some(edit.0); + for indel in edit.1.into_iter() { text_edit_builder.replace(indel.delete, indel.insert); } } @@ -895,12 +882,11 @@ mod foo$0; RangeInfo { range: 4..7, info: SourceChange { - source_file_edits: [ - SourceFileEdit { - file_id: FileId( + source_file_edits: SourceFileEdits { + edits: { + FileId( 1, - ), - edit: TextEdit { + ): TextEdit { indels: [ Indel { insert: "foo2", @@ -909,7 +895,7 @@ mod foo$0; ], }, }, - ], + }, file_system_edits: [ MoveFile { src: FileId( @@ -950,12 +936,11 @@ use crate::foo$0::FooContent; RangeInfo { range: 11..14, info: SourceChange { - source_file_edits: [ - SourceFileEdit { - file_id: FileId( + source_file_edits: SourceFileEdits { + edits: { + FileId( 0, - ), - edit: TextEdit { + ): TextEdit { indels: [ Indel { insert: "quux", @@ -963,12 +948,9 @@ use crate::foo$0::FooContent; }, ], }, - }, - SourceFileEdit { - file_id: FileId( + FileId( 2, - ), - edit: TextEdit { + ): TextEdit { indels: [ Indel { insert: "quux", @@ -977,7 +959,7 @@ use crate::foo$0::FooContent; ], }, }, - ], + }, file_system_edits: [ MoveFile { src: FileId( @@ -1012,12 +994,11 @@ mod fo$0o; RangeInfo { range: 4..7, info: SourceChange { - source_file_edits: [ - SourceFileEdit { - file_id: FileId( + source_file_edits: SourceFileEdits { + edits: { + FileId( 0, - ), - edit: TextEdit { + ): TextEdit { indels: [ Indel { insert: "foo2", @@ -1026,7 +1007,7 @@ mod fo$0o; ], }, }, - ], + }, file_system_edits: [ MoveFile { src: FileId( @@ -1062,12 +1043,11 @@ mod outer { mod fo$0o; } RangeInfo { range: 16..19, info: SourceChange { - source_file_edits: [ - SourceFileEdit { - file_id: FileId( + source_file_edits: SourceFileEdits { + edits: { + FileId( 0, - ), - edit: TextEdit { + ): TextEdit { indels: [ Indel { insert: "bar", @@ -1076,7 +1056,7 @@ mod outer { mod fo$0o; } ], }, }, - ], + }, file_system_edits: [ MoveFile { src: FileId( @@ -1135,34 +1115,30 @@ pub mod foo$0; RangeInfo { range: 8..11, info: SourceChange { - source_file_edits: [ - SourceFileEdit { - file_id: FileId( - 1, - ), - edit: TextEdit { + source_file_edits: SourceFileEdits { + edits: { + FileId( + 0, + ): TextEdit { indels: [ Indel { insert: "foo2", - delete: 8..11, + delete: 27..30, }, ], }, - }, - SourceFileEdit { - file_id: FileId( - 0, - ), - edit: TextEdit { + FileId( + 1, + ): TextEdit { indels: [ Indel { insert: "foo2", - delete: 27..30, + delete: 8..11, }, ], }, }, - ], + }, file_system_edits: [ MoveFile { src: FileId( diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 88c905003..b3fc32645 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -15,8 +15,11 @@ mod on_enter; -use ide_db::base_db::{FilePosition, SourceDatabase}; -use ide_db::{source_change::SourceFileEdit, RootDatabase}; +use ide_db::{ + base_db::{FilePosition, SourceDatabase}, + source_change::SourceFileEdits, + RootDatabase, +}; use syntax::{ algo::find_node_at_offset, ast::{self, edit::IndentLevel, AstToken}, @@ -56,7 +59,7 @@ pub(crate) fn on_char_typed( let file = &db.parse(position.file_id).tree(); assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); let edit = on_char_typed_inner(file, position.offset, char_typed)?; - Some(SourceFileEdit { file_id: position.file_id, edit }.into()) + Some(SourceFileEdits::from_text_edit(position.file_id, edit).into()) } fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option { diff --git a/crates/ide_db/src/source_change.rs b/crates/ide_db/src/source_change.rs index 10c0abdac..516d7859a 100644 --- a/crates/ide_db/src/source_change.rs +++ b/crates/ide_db/src/source_change.rs @@ -3,12 +3,18 @@ //! //! It can be viewed as a dual for `AnalysisChange`. +use std::{ + collections::hash_map::Entry, + iter::{self, FromIterator}, +}; + use base_db::{AnchoredPathBuf, FileId}; +use rustc_hash::FxHashMap; use text_edit::TextEdit; #[derive(Default, Debug, Clone)] pub struct SourceChange { - pub source_file_edits: Vec, + pub source_file_edits: SourceFileEdits, pub file_system_edits: Vec, pub is_snippet: bool, } @@ -17,27 +23,51 @@ impl SourceChange { /// Creates a new SourceChange with the given label /// from the edits. pub fn from_edits( - source_file_edits: Vec, + source_file_edits: SourceFileEdits, file_system_edits: Vec, ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } } } -#[derive(Debug, Clone)] -pub struct SourceFileEdit { - pub file_id: FileId, - pub edit: TextEdit, +#[derive(Default, Debug, Clone)] +pub struct SourceFileEdits { + pub edits: FxHashMap, +} + +impl SourceFileEdits { + pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self { + SourceFileEdits { edits: FxHashMap::from_iter(iter::once((file_id, edit))) } + } + + pub fn len(&self) -> usize { + self.edits.len() + } + + pub fn is_empty(&self) -> bool { + self.edits.is_empty() + } + + pub fn insert(&mut self, file_id: FileId, edit: TextEdit) { + match self.edits.entry(file_id) { + Entry::Occupied(mut entry) => { + entry.get_mut().union(edit).expect("overlapping edits for same file"); + } + Entry::Vacant(entry) => { + entry.insert(edit); + } + } + } } -impl From for SourceChange { - fn from(edit: SourceFileEdit) -> SourceChange { - vec![edit].into() +impl Extend<(FileId, TextEdit)> for SourceFileEdits { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|(file_id, edit)| self.insert(file_id, edit)); } } -impl From> for SourceChange { - fn from(source_file_edits: Vec) -> SourceChange { +impl From for SourceChange { + fn from(source_file_edits: SourceFileEdits) -> SourceChange { SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } @@ -51,7 +81,7 @@ pub enum FileSystemEdit { impl From for SourceChange { fn from(edit: FileSystemEdit) -> SourceChange { SourceChange { - source_file_edits: Vec::new(), + source_file_edits: Default::default(), file_system_edits: vec![edit], is_snippet: false, } diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs index a06631dac..100331c37 100644 --- a/crates/rust-analyzer/src/cli/ssr.rs +++ b/crates/rust-analyzer/src/cli/ssr.rs @@ -12,10 +12,10 @@ pub fn apply_ssr_rules(rules: Vec) -> Result<()> { match_finder.add_rule(rule)?; } let edits = match_finder.edits(); - for edit in edits { - if let Some(path) = vfs.file_path(edit.file_id).as_path() { - let mut contents = db.file_text(edit.file_id).to_string(); - edit.edit.apply(&mut contents); + for (file_id, edit) in edits.edits { + if let Some(path) = vfs.file_path(file_id).as_path() { + let mut contents = db.file_text(file_id).to_string(); + edit.apply(&mut contents); std::fs::write(path, contents)?; } } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index dc81f55d6..839466e4f 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -260,15 +260,15 @@ pub(crate) fn handle_on_type_formatting( } let edit = snap.analysis.on_char_typed(position, char_typed)?; - let mut edit = match edit { + let edit = match edit { Some(it) => it, None => return Ok(None), }; // This should be a single-file edit - let edit = edit.source_file_edits.pop().unwrap(); + let (_, edit) = edit.source_file_edits.edits.into_iter().next().unwrap(); - let change = to_proto::text_edit_vec(&line_index, line_endings, edit.edit); + let change = to_proto::text_edit_vec(&line_index, line_endings, edit); Ok(Some(change)) } @@ -463,8 +463,12 @@ pub(crate) fn handle_will_rename_files( .collect(); // Drop file system edits since we're just renaming things on the same level - let edits = source_changes.into_iter().map(|it| it.source_file_edits).flatten().collect(); - let source_change = SourceChange::from_edits(edits, Vec::new()); + let mut source_changes = source_changes.into_iter(); + let mut source_file_edits = + source_changes.next().map_or_else(Default::default, |it| it.source_file_edits); + // no collect here because we want to merge text edits on same file ids + source_file_edits.extend(source_changes.map(|it| it.source_file_edits.edits).flatten()); + let source_change = SourceChange::from_edits(source_file_edits, Vec::new()); let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; Ok(Some(workspace_edit)) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index a7ff8975a..34f510e73 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -8,8 +8,7 @@ use ide::{ Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, - RenameError, Runnable, Severity, SourceChange, SourceFileEdit, SymbolKind, TextEdit, TextRange, - TextSize, + RenameError, Runnable, Severity, SourceChange, SymbolKind, TextEdit, TextRange, TextSize, }; use itertools::Itertools; @@ -634,13 +633,13 @@ pub(crate) fn goto_definition_response( pub(crate) fn snippet_text_document_edit( snap: &GlobalStateSnapshot, is_snippet: bool, - source_file_edit: SourceFileEdit, + file_id: FileId, + edit: TextEdit, ) -> Result { - let text_document = optional_versioned_text_document_identifier(snap, source_file_edit.file_id); - let line_index = snap.analysis.file_line_index(source_file_edit.file_id)?; - let line_endings = snap.file_line_endings(source_file_edit.file_id); - let edits = source_file_edit - .edit + let text_document = optional_versioned_text_document_identifier(snap, file_id); + let line_index = snap.analysis.file_line_index(file_id)?; + let line_endings = snap.file_line_endings(file_id); + let edits = edit .into_iter() .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it)) .collect(); @@ -699,8 +698,8 @@ pub(crate) fn snippet_workspace_edit( let ops = snippet_text_document_ops(snap, op); document_changes.extend_from_slice(&ops); } - for edit in source_change.source_file_edits { - let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?; + for (file_id, edit) in source_change.source_file_edits.edits { + let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let workspace_edit = diff --git a/crates/ssr/src/lib.rs b/crates/ssr/src/lib.rs index 747ce495d..d99ebefb1 100644 --- a/crates/ssr/src/lib.rs +++ b/crates/ssr/src/lib.rs @@ -74,8 +74,10 @@ pub use crate::errors::SsrError; pub use crate::matching::Match; use crate::matching::MatchFailureReason; use hir::Semantics; -use ide_db::base_db::{FileId, FilePosition, FileRange}; -use ide_db::source_change::SourceFileEdit; +use ide_db::{ + base_db::{FileId, FilePosition, FileRange}, + source_change::SourceFileEdits, +}; use resolving::ResolvedRule; use rustc_hash::FxHashMap; use syntax::{ast, AstNode, SyntaxNode, TextRange}; @@ -159,7 +161,7 @@ impl<'db> MatchFinder<'db> { } /// Finds matches for all added rules and returns edits for all found matches. - pub fn edits(&self) -> Vec { + pub fn edits(&self) -> SourceFileEdits { use ide_db::base_db::SourceDatabaseExt; let mut matches_by_file = FxHashMap::default(); for m in self.matches().matches { @@ -169,13 +171,21 @@ impl<'db> MatchFinder<'db> { .matches .push(m); } - let mut edits = vec![]; - for (file_id, matches) in matches_by_file { - let edit = - replacing::matches_to_edit(&matches, &self.sema.db.file_text(file_id), &self.rules); - edits.push(SourceFileEdit { file_id, edit }); + SourceFileEdits { + edits: matches_by_file + .into_iter() + .map(|(file_id, matches)| { + ( + file_id, + replacing::matches_to_edit( + &matches, + &self.sema.db.file_text(file_id), + &self.rules, + ), + ) + }) + .collect(), } - edits } /// 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 6cf831431..5888bf8f8 100644 --- a/crates/ssr/src/matching.rs +++ b/crates/ssr/src/matching.rs @@ -810,9 +810,9 @@ mod tests { let edits = match_finder.edits(); assert_eq!(edits.len(), 1); - let edit = &edits[0]; + let edit = &edits.edits[&position.file_id]; let mut after = input.to_string(); - edit.edit.apply(&mut after); + edit.apply(&mut after); 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 d6918c22d..8ba783526 100644 --- a/crates/ssr/src/tests.rs +++ b/crates/ssr/src/tests.rs @@ -103,11 +103,10 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { if edits.is_empty() { panic!("No edits were made"); } - assert_eq!(edits[0].file_id, position.file_id); // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters // stuff. let mut actual = db.file_text(position.file_id).to_string(); - edits[0].edit.apply(&mut actual); + edits.edits[&position.file_id].apply(&mut actual); expected.assert_eq(&actual); } -- cgit v1.2.3 From e23bfafb32a235fdb60ba279ea68b5aa381c2110 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 14 Jan 2021 22:43:09 +0100 Subject: Fix assert_never invoking assert_always --- crates/stdx/src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdx/src/macros.rs b/crates/stdx/src/macros.rs index 263b938e3..4f5c6100d 100644 --- a/crates/stdx/src/macros.rs +++ b/crates/stdx/src/macros.rs @@ -66,7 +66,7 @@ macro_rules! impl_from { /// Shamelessly stolen from: https://www.sqlite.org/assert.html #[macro_export] macro_rules! assert_never { - ($cond:expr) => { $crate::assert_always!($cond, "") }; + ($cond:expr) => { $crate::assert_never!($cond, "") }; ($cond:expr, $($fmt:tt)*) => {{ let value = $cond; if value { -- cgit v1.2.3 From d5095329a1c12e93653d8de4a93f0b4f5cad4c6e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 14 Jan 2021 22:43:36 +0100 Subject: Phase out SourceFileEdits in favour of a plain HashMap --- crates/assists/src/assist_context.rs | 28 ++-- crates/assists/src/tests.rs | 25 ++-- crates/ide/src/diagnostics.rs | 12 +- crates/ide/src/diagnostics/field_shorthand.rs | 6 +- crates/ide/src/diagnostics/fixes.rs | 18 +-- crates/ide/src/lib.rs | 2 +- crates/ide/src/references/rename.rs | 195 +++++++++++++------------- crates/ide/src/typing.rs | 3 +- crates/ide_db/src/source_change.rs | 50 +++---- crates/rust-analyzer/src/cli/ssr.rs | 2 +- crates/rust-analyzer/src/handlers.rs | 10 +- crates/rust-analyzer/src/to_proto.rs | 2 +- crates/ssr/src/lib.rs | 36 +++-- crates/ssr/src/matching.rs | 2 +- crates/ssr/src/tests.rs | 2 +- 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::{ }; use ide_db::{ label::Label, - source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, + source_change::{FileSystemEdit, SourceChange}, RootDatabase, }; use syntax::{ @@ -180,20 +180,12 @@ impl Assists { pub(crate) struct AssistBuilder { edit: TextEditBuilder, file_id: FileId, - is_snippet: bool, - source_file_edits: SourceFileEdits, - file_system_edits: Vec, + source_change: SourceChange, } impl AssistBuilder { pub(crate) fn new(file_id: FileId) -> AssistBuilder { - AssistBuilder { - edit: TextEdit::builder(), - file_id, - is_snippet: false, - source_file_edits: SourceFileEdits::default(), - file_system_edits: Vec::default(), - } + AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() } } pub(crate) fn edit_file(&mut self, file_id: FileId) { @@ -204,7 +196,7 @@ impl AssistBuilder { fn commit(&mut self) { let edit = mem::take(&mut self.edit).finish(); if !edit.is_empty() { - self.source_file_edits.insert(self.file_id, edit); + self.source_change.insert_source_edit(self.file_id, edit); } } @@ -223,7 +215,7 @@ impl AssistBuilder { offset: TextSize, snippet: impl Into, ) { - self.is_snippet = true; + self.source_change.is_snippet = true; self.insert(offset, snippet); } /// Replaces specified `range` of text with a given string. @@ -237,7 +229,7 @@ impl AssistBuilder { range: TextRange, snippet: impl Into, ) { - self.is_snippet = true; + self.source_change.is_snippet = true; self.replace(range, snippet); } pub(crate) fn replace_ast(&mut self, old: N, new: N) { @@ -252,15 +244,11 @@ impl AssistBuilder { pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into) { let file_system_edit = FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() }; - self.file_system_edits.push(file_system_edit); + self.source_change.push_file_system_edit(file_system_edit); } fn finish(mut self) -> SourceChange { self.commit(); - SourceChange { - source_file_edits: mem::take(&mut self.source_file_edits), - file_system_edits: mem::take(&mut self.file_system_edits), - is_snippet: self.is_snippet, - } + mem::take(&mut self.source_change) } } 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) { let actual = { let source_change = assist.source_change.unwrap(); let mut actual = before; - if let Some(source_file_edit) = source_change.source_file_edits.edits.get(&file_id) { + if let Some(source_file_edit) = source_change.get_source_edit(file_id) { source_file_edit.apply(&mut actual); } actual @@ -120,7 +120,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: && source_change.file_system_edits.len() == 0; let mut buf = String::new(); - for (file_id, edit) in source_change.source_file_edits.edits { + for (file_id, edit) in source_change.source_file_edits { let mut text = db.file_text(file_id).as_ref().to_owned(); edit.apply(&mut text); if !skip_header { @@ -132,18 +132,15 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: buf.push_str(&text); } - for file_system_edit in source_change.file_system_edits.clone() { - match file_system_edit { - FileSystemEdit::CreateFile { dst, initial_contents } => { - let sr = db.file_source_root(dst.anchor); - let sr = db.source_root(sr); - let mut base = sr.path_for_file(&dst.anchor).unwrap().clone(); - base.pop(); - let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]); - format_to!(buf, "//- {}\n", created_file_path); - buf.push_str(&initial_contents); - } - _ => (), + for file_system_edit in source_change.file_system_edits { + if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit { + let sr = db.file_source_root(dst.anchor); + let sr = db.source_root(sr); + let mut base = sr.path_for_file(&dst.anchor).unwrap().clone(); + base.pop(); + let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]); + format_to!(buf, "//- {}\n", created_file_path); + buf.push_str(&initial_contents); } } 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::{ diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, Semantics, }; -use ide_db::{base_db::SourceDatabase, source_change::SourceFileEdits, RootDatabase}; +use ide_db::{base_db::SourceDatabase, RootDatabase}; use itertools::Itertools; use rustc_hash::FxHashSet; use syntax::{ @@ -219,7 +219,7 @@ fn check_unnecessary_braces_in_use_statement( Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) .with_fix(Some(Fix::new( "Remove unnecessary braces", - SourceFileEdits::from_text_edit(file_id, edit).into(), + SourceChange::from_text_edit(file_id, edit), use_range, ))), ); @@ -264,10 +264,10 @@ mod tests { .unwrap(); let fix = diagnostic.fix.unwrap(); let actual = { - let file_id = *fix.source_change.source_file_edits.edits.keys().next().unwrap(); + let file_id = *fix.source_change.source_file_edits.keys().next().unwrap(); let mut actual = analysis.file_text(file_id).unwrap().to_string(); - for edit in fix.source_change.source_file_edits.edits.values() { + for edit in fix.source_change.source_file_edits.values() { edit.apply(&mut actual); } actual @@ -613,9 +613,7 @@ fn test_fn() { Fix { label: "Create module", source_change: SourceChange { - source_file_edits: SourceFileEdits { - edits: {}, - }, + source_file_edits: {}, file_system_edits: [ CreateFile { 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 @@ //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both //! expressions and patterns. -use ide_db::{base_db::FileId, source_change::SourceFileEdits}; +use ide_db::{base_db::FileId, source_change::SourceChange}; use syntax::{ast, match_ast, AstNode, SyntaxNode}; use text_edit::TextEdit; @@ -49,7 +49,7 @@ fn check_expr_field_shorthand( Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( Some(Fix::new( "Use struct shorthand initialization", - SourceFileEdits::from_text_edit(file_id, edit).into(), + SourceChange::from_text_edit(file_id, edit), field_range, )), ), @@ -88,7 +88,7 @@ fn check_pat_field_shorthand( acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( Some(Fix::new( "Use struct field shorthand", - SourceFileEdits::from_text_edit(file_id, edit).into(), + SourceChange::from_text_edit(file_id, edit), field_range, )), )); 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::{ }; use ide_db::{ base_db::{AnchoredPathBuf, FileId}, - source_change::{FileSystemEdit, SourceFileEdits}, + source_change::{FileSystemEdit, SourceChange}, RootDatabase, }; use syntax::{ @@ -88,7 +88,7 @@ impl DiagnosticWithFix for MissingFields { }; Some(Fix::new( "Fill struct fields", - SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(), + SourceChange::from_text_edit(self.file.original_file(sema.db), edit), sema.original_range(&field_list_parent.syntax()).range, )) } @@ -101,8 +101,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { let tail_expr_range = tail_expr.syntax().text_range(); let replacement = format!("{}({})", self.required, tail_expr.syntax()); let edit = TextEdit::replace(tail_expr_range, replacement); - let source_change = - SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(); + let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; Some(Fix::new(name, source_change, tail_expr_range)) } @@ -122,8 +121,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon { .text_range(); let edit = TextEdit::delete(semicolon); - let source_change = - SourceFileEdits::from_text_edit(self.file.original_file(sema.db), edit).into(); + let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); Some(Fix::new("Remove this semicolon", source_change, semicolon)) } @@ -204,15 +202,11 @@ fn missing_record_expr_field_fix( new_field = format!(",{}", new_field); } - let source_change = SourceFileEdits::from_text_edit( + let source_change = SourceChange::from_text_edit( def_file_id, TextEdit::insert(last_field_syntax.text_range().end(), new_field), ); - return Some(Fix::new( - "Create field", - source_change.into(), - record_expr_field.syntax().text_range(), - )); + return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range())); fn record_field_list(field_def_list: ast::FieldList) -> Option { 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::{ label::Label, line_index::{LineCol, LineIndex}, search::SearchScope, - source_change::{FileSystemEdit, SourceChange, SourceFileEdits}, + source_change::{FileSystemEdit, SourceChange}, symbol_index::Query, RootDatabase, }; 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; use crate::{ FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, - SourceFileEdits, TextRange, TextSize, + TextRange, TextSize, }; type RenameResult = Result; @@ -249,8 +249,8 @@ fn rename_mod( if IdentifierKind::Ident != check_identifier(new_name)? { bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); } - let mut source_file_edits = SourceFileEdits::default(); - let mut file_system_edits = Vec::new(); + + let mut source_change = SourceChange::default(); let src = module.definition_source(sema.db); let file_id = src.file_id.original_file(sema.db); @@ -264,7 +264,7 @@ fn rename_mod( }; let dst = AnchoredPathBuf { anchor: file_id, path }; let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; - file_system_edits.push(move_file); + source_change.push_file_system_edit(move_file); } ModuleSource::Module(..) => {} } @@ -272,17 +272,19 @@ fn rename_mod( if let Some(src) = module.declaration_source(sema.db) { let file_id = src.file_id.original_file(sema.db); let name = src.value.name().unwrap(); - source_file_edits - .insert(file_id, TextEdit::replace(name.syntax().text_range(), new_name.into())); + source_change.insert_source_edit( + file_id, + TextEdit::replace(name.syntax().text_range(), new_name.into()), + ); } let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; let ref_edits = refs.references().iter().map(|(&file_id, references)| { source_edit_from_references(sema, file_id, references, new_name) }); - source_file_edits.extend(ref_edits); + source_change.extend(ref_edits); - Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) + Ok(RangeInfo::new(range, source_change)) } fn rename_to_self( @@ -331,13 +333,16 @@ fn rename_to_self( let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; - let mut edits = SourceFileEdits::default(); - edits.extend(refs.references().iter().map(|(&file_id, references)| { + let mut source_change = SourceChange::default(); + source_change.extend(refs.references().iter().map(|(&file_id, references)| { source_edit_from_references(sema, file_id, references, "self") })); - edits.insert(position.file_id, TextEdit::replace(param_range, String::from(self_param))); + source_change.insert_source_edit( + position.file_id, + TextEdit::replace(param_range, String::from(self_param)), + ); - Ok(RangeInfo::new(range, edits.into())) + Ok(RangeInfo::new(range, source_change)) } fn text_edit_from_self_param( @@ -391,7 +396,7 @@ fn rename_self_to_param( .ok_or_else(|| format_err!("No surrounding method declaration found"))?; let search_range = fn_def.syntax().text_range(); - let mut edits = SourceFileEdits::default(); + let mut source_change = SourceChange::default(); for (idx, _) in text.match_indices("self") { let offset: TextSize = idx.try_into().unwrap(); @@ -405,18 +410,18 @@ fn rename_self_to_param( } else { TextEdit::replace(usage.text_range(), String::from(new_name)) }; - edits.insert(position.file_id, edit); + source_change.insert_source_edit(position.file_id, edit); } } - if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { + if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore { bail!("Cannot rename reference to `_` as it is being referenced multiple times"); } let range = ast::SelfParam::cast(self_token.parent()) .map_or(self_token.text_range(), |p| p.syntax().text_range()); - Ok(RangeInfo::new(range, edits.into())) + Ok(RangeInfo::new(range, source_change)) } fn rename_reference( @@ -453,12 +458,12 @@ fn rename_reference( (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), } - let mut edits = SourceFileEdits::default(); - edits.extend(refs.into_iter().map(|(file_id, references)| { + let mut source_change = SourceChange::default(); + source_change.extend(refs.into_iter().map(|(file_id, references)| { source_edit_from_references(sema, file_id, &references, new_name) })); - Ok(RangeInfo::new(range, edits.into())) + Ok(RangeInfo::new(range, source_change)) } #[cfg(test)] @@ -480,7 +485,7 @@ mod tests { Ok(source_change) => { let mut text_edit_builder = TextEdit::builder(); let mut file_id: Option = None; - for edit in source_change.info.source_file_edits.edits { + for edit in source_change.info.source_file_edits { file_id = Some(edit.0); for indel in edit.1.into_iter() { text_edit_builder.replace(indel.delete, indel.insert); @@ -882,18 +887,16 @@ mod foo$0; RangeInfo { range: 4..7, info: SourceChange { - source_file_edits: SourceFileEdits { - edits: { - FileId( - 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + source_file_edits: { + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], }, }, file_system_edits: [ @@ -936,28 +939,26 @@ use crate::foo$0::FooContent; RangeInfo { range: 11..14, info: SourceChange { - source_file_edits: SourceFileEdits { - edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 8..11, - }, - ], - }, - FileId( - 2, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 11..14, - }, - ], - }, + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 8..11, + }, + ], + }, + FileId( + 2, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 11..14, + }, + ], }, }, file_system_edits: [ @@ -994,18 +995,16 @@ mod fo$0o; RangeInfo { range: 4..7, info: SourceChange { - source_file_edits: SourceFileEdits { - edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], }, }, file_system_edits: [ @@ -1043,18 +1042,16 @@ mod outer { mod fo$0o; } RangeInfo { range: 16..19, info: SourceChange { - source_file_edits: SourceFileEdits { - edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "bar", - delete: 16..19, - }, - ], - }, + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "bar", + delete: 16..19, + }, + ], }, }, file_system_edits: [ @@ -1115,28 +1112,26 @@ pub mod foo$0; RangeInfo { range: 8..11, info: SourceChange { - source_file_edits: SourceFileEdits { - edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 27..30, - }, - ], - }, - FileId( - 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 8..11, - }, - ], - }, + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 27..30, + }, + ], + }, + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 8..11, + }, + ], }, }, 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; use ide_db::{ base_db::{FilePosition, SourceDatabase}, - source_change::SourceFileEdits, RootDatabase, }; use syntax::{ @@ -59,7 +58,7 @@ pub(crate) fn on_char_typed( let file = &db.parse(position.file_id).tree(); assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); let edit = on_char_typed_inner(file, position.offset, char_typed)?; - Some(SourceFileEdits::from_text_edit(position.file_id, edit).into()) + Some(SourceChange::from_text_edit(position.file_id, edit)) } fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option { 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::{ use base_db::{AnchoredPathBuf, FileId}; use rustc_hash::FxHashMap; +use stdx::assert_never; use text_edit::TextEdit; #[derive(Default, Debug, Clone)] pub struct SourceChange { - pub source_file_edits: SourceFileEdits, + pub source_file_edits: FxHashMap, pub file_system_edits: Vec, pub is_snippet: bool, } @@ -23,51 +24,50 @@ impl SourceChange { /// Creates a new SourceChange with the given label /// from the edits. pub fn from_edits( - source_file_edits: SourceFileEdits, + source_file_edits: FxHashMap, file_system_edits: Vec, ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } } -} - -#[derive(Default, Debug, Clone)] -pub struct SourceFileEdits { - pub edits: FxHashMap, -} -impl SourceFileEdits { pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self { - SourceFileEdits { edits: FxHashMap::from_iter(iter::once((file_id, edit))) } - } - - pub fn len(&self) -> usize { - self.edits.len() - } - - pub fn is_empty(&self) -> bool { - self.edits.is_empty() + SourceChange { + source_file_edits: FxHashMap::from_iter(iter::once((file_id, edit))), + ..Default::default() + } } - pub fn insert(&mut self, file_id: FileId, edit: TextEdit) { - match self.edits.entry(file_id) { + pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) { + match self.source_file_edits.entry(file_id) { Entry::Occupied(mut entry) => { - entry.get_mut().union(edit).expect("overlapping edits for same file"); + assert_never!( + entry.get_mut().union(edit).is_err(), + "overlapping edits for same file" + ); } Entry::Vacant(entry) => { entry.insert(edit); } } } + + pub fn push_file_system_edit(&mut self, edit: FileSystemEdit) { + self.file_system_edits.push(edit); + } + + pub fn get_source_edit(&self, file_id: FileId) -> Option<&TextEdit> { + self.source_file_edits.get(&file_id) + } } -impl Extend<(FileId, TextEdit)> for SourceFileEdits { +impl Extend<(FileId, TextEdit)> for SourceChange { fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|(file_id, edit)| self.insert(file_id, edit)); + iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit)); } } -impl From for SourceChange { - fn from(source_file_edits: SourceFileEdits) -> SourceChange { +impl From> for SourceChange { + fn from(source_file_edits: FxHashMap) -> SourceChange { SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } 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) -> Result<()> { match_finder.add_rule(rule)?; } let edits = match_finder.edits(); - for (file_id, edit) in edits.edits { + for (file_id, edit) in edits { if let Some(path) = vfs.file_path(file_id).as_path() { let mut contents = db.file_text(file_id).to_string(); 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( }; // This should be a single-file edit - let (_, edit) = edit.source_file_edits.edits.into_iter().next().unwrap(); + let (_, edit) = edit.source_file_edits.into_iter().next().unwrap(); let change = to_proto::text_edit_vec(&line_index, line_endings, edit); Ok(Some(change)) @@ -464,12 +464,10 @@ pub(crate) fn handle_will_rename_files( // Drop file system edits since we're just renaming things on the same level let mut source_changes = source_changes.into_iter(); - let mut source_file_edits = - source_changes.next().map_or_else(Default::default, |it| it.source_file_edits); + let mut source_change = source_changes.next().unwrap_or_default(); + source_change.file_system_edits.clear(); // no collect here because we want to merge text edits on same file ids - source_file_edits.extend(source_changes.map(|it| it.source_file_edits.edits).flatten()); - let source_change = SourceChange::from_edits(source_file_edits, Vec::new()); - + source_change.extend(source_changes.map(|it| it.source_file_edits).flatten()); let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; Ok(Some(workspace_edit)) } 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( let ops = snippet_text_document_ops(snap, op); document_changes.extend_from_slice(&ops); } - for (file_id, edit) in source_change.source_file_edits.edits { + for (file_id, edit) in source_change.source_file_edits { let edit = snippet_text_document_edit(&snap, source_change.is_snippet, file_id, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } 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; pub use crate::matching::Match; use crate::matching::MatchFailureReason; use hir::Semantics; -use ide_db::{ - base_db::{FileId, FilePosition, FileRange}, - source_change::SourceFileEdits, -}; +use ide_db::base_db::{FileId, FilePosition, FileRange}; use resolving::ResolvedRule; use rustc_hash::FxHashMap; use syntax::{ast, AstNode, SyntaxNode, TextRange}; +use text_edit::TextEdit; // A structured search replace rule. Create by calling `parse` on a str. #[derive(Debug)] @@ -161,7 +159,7 @@ impl<'db> MatchFinder<'db> { } /// Finds matches for all added rules and returns edits for all found matches. - pub fn edits(&self) -> SourceFileEdits { + pub fn edits(&self) -> FxHashMap { use ide_db::base_db::SourceDatabaseExt; let mut matches_by_file = FxHashMap::default(); for m in self.matches().matches { @@ -171,21 +169,19 @@ impl<'db> MatchFinder<'db> { .matches .push(m); } - SourceFileEdits { - edits: matches_by_file - .into_iter() - .map(|(file_id, matches)| { - ( - file_id, - replacing::matches_to_edit( - &matches, - &self.sema.db.file_text(file_id), - &self.rules, - ), - ) - }) - .collect(), - } + matches_by_file + .into_iter() + .map(|(file_id, matches)| { + ( + file_id, + replacing::matches_to_edit( + &matches, + &self.sema.db.file_text(file_id), + &self.rules, + ), + ) + }) + .collect() } /// 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 { let edits = match_finder.edits(); assert_eq!(edits.len(), 1); - let edit = &edits.edits[&position.file_id]; + let edit = &edits[&position.file_id]; let mut after = input.to_string(); edit.apply(&mut after); 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) { // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters // stuff. let mut actual = db.file_text(position.file_id).to_string(); - edits.edits[&position.file_id].apply(&mut actual); + edits[&position.file_id].apply(&mut actual); expected.assert_eq(&actual); } -- cgit v1.2.3