mirror of
https://github.com/rust-lang/rust.git
synced 2024-12-09 07:04:02 +00:00
Merge #7272
7272: Group file source edits by FileId r=matklad a=Veykril Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
054e206152
@ -10,7 +10,7 @@ use ide_db::{
|
||||
};
|
||||
use ide_db::{
|
||||
label::Label,
|
||||
source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
|
||||
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: Vec<SourceFileEdit>,
|
||||
file_system_edits: Vec<FileSystemEdit>,
|
||||
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: Vec::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,15 +196,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_change.insert_source_edit(self.file_id, edit);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +215,7 @@ impl AssistBuilder {
|
||||
offset: TextSize,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.is_snippet = true;
|
||||
self.source_change.is_snippet = true;
|
||||
self.insert(offset, snippet);
|
||||
}
|
||||
/// Replaces specified `range` of text with a given string.
|
||||
@ -245,7 +229,7 @@ impl AssistBuilder {
|
||||
range: TextRange,
|
||||
snippet: impl Into<String>,
|
||||
) {
|
||||
self.is_snippet = true;
|
||||
self.source_change.is_snippet = true;
|
||||
self.replace(range, snippet);
|
||||
}
|
||||
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
||||
@ -260,15 +244,11 @@ impl AssistBuilder {
|
||||
pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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.get_source_edit(file_id) {
|
||||
source_file_edit.apply(&mut actual);
|
||||
}
|
||||
actual
|
||||
};
|
||||
@ -116,37 +114,33 @@ 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, 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(),
|
||||
SourceChange::from_text_edit(file_id, edit),
|
||||
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.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.values() {
|
||||
edit.apply(&mut actual);
|
||||
}
|
||||
actual
|
||||
};
|
||||
@ -616,7 +613,7 @@ fn test_fn() {
|
||||
Fix {
|
||||
label: "Create module",
|
||||
source_change: SourceChange {
|
||||
source_file_edits: [],
|
||||
source_file_edits: {},
|
||||
file_system_edits: [
|
||||
CreateFile {
|
||||
dst: AnchoredPathBuf {
|
||||
|
@ -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::SourceChange};
|
||||
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(),
|
||||
SourceChange::from_text_edit(file_id, edit),
|
||||
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(),
|
||||
SourceChange::from_text_edit(file_id, edit),
|
||||
field_range,
|
||||
)),
|
||||
));
|
||||
|
@ -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, SourceChange},
|
||||
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(),
|
||||
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 =
|
||||
SourceFileEdit { file_id: 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 =
|
||||
SourceFileEdit { file_id: 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 = SourceFileEdit {
|
||||
file_id: def_file_id,
|
||||
edit: 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(),
|
||||
));
|
||||
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, record_expr_field.syntax().text_range()));
|
||||
|
||||
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
|
||||
match field_def_list {
|
||||
|
@ -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},
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ use text_edit::TextEdit;
|
||||
|
||||
use crate::{
|
||||
FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
|
||||
SourceFileEdit, TextRange, TextSize,
|
||||
TextRange, TextSize,
|
||||
};
|
||||
|
||||
type RenameResult<T> = Result<T, RenameError>;
|
||||
@ -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,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 = Vec::new();
|
||||
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);
|
||||
@ -265,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(..) => {}
|
||||
}
|
||||
@ -273,20 +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();
|
||||
let edit = SourceFileEdit {
|
||||
source_change.insert_source_edit(
|
||||
file_id,
|
||||
edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
|
||||
};
|
||||
source_file_edits.push(edit);
|
||||
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(
|
||||
@ -335,20 +333,16 @@ 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::<Vec<_>>();
|
||||
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")
|
||||
}));
|
||||
source_change.insert_source_edit(
|
||||
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, source_change))
|
||||
}
|
||||
|
||||
fn text_edit_from_self_param(
|
||||
@ -402,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: Vec<SourceFileEdit> = vec![];
|
||||
let mut source_change = SourceChange::default();
|
||||
|
||||
for (idx, _) in text.match_indices("self") {
|
||||
let offset: TextSize = idx.try_into().unwrap();
|
||||
@ -416,18 +410,18 @@ fn rename_self_to_param(
|
||||
} else {
|
||||
TextEdit::replace(usage.text_range(), String::from(new_name))
|
||||
};
|
||||
edits.push(SourceFileEdit { file_id: 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, SourceChange::from(edits)))
|
||||
Ok(RangeInfo::new(range, source_change))
|
||||
}
|
||||
|
||||
fn rename_reference(
|
||||
@ -464,14 +458,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::<Vec<_>>();
|
||||
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, SourceChange::from(edit)))
|
||||
Ok(RangeInfo::new(range, source_change))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -494,8 +486,8 @@ mod tests {
|
||||
let mut text_edit_builder = TextEdit::builder();
|
||||
let mut file_id: Option<FileId> = None;
|
||||
for edit in source_change.info.source_file_edits {
|
||||
file_id = Some(edit.file_id);
|
||||
for indel in edit.edit.into_iter() {
|
||||
file_id = Some(edit.0);
|
||||
for indel in edit.1.into_iter() {
|
||||
text_edit_builder.replace(indel.delete, indel.insert);
|
||||
}
|
||||
}
|
||||
@ -895,21 +887,18 @@ mod foo$0;
|
||||
RangeInfo {
|
||||
range: 4..7,
|
||||
info: SourceChange {
|
||||
source_file_edits: [
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 4..7,
|
||||
},
|
||||
],
|
||||
},
|
||||
source_file_edits: {
|
||||
FileId(
|
||||
1,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 4..7,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
file_system_edits: [
|
||||
MoveFile {
|
||||
src: FileId(
|
||||
@ -950,34 +939,28 @@ use crate::foo$0::FooContent;
|
||||
RangeInfo {
|
||||
range: 11..14,
|
||||
info: SourceChange {
|
||||
source_file_edits: [
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "quux",
|
||||
delete: 8..11,
|
||||
},
|
||||
],
|
||||
},
|
||||
source_file_edits: {
|
||||
FileId(
|
||||
0,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "quux",
|
||||
delete: 8..11,
|
||||
},
|
||||
],
|
||||
},
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
2,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "quux",
|
||||
delete: 11..14,
|
||||
},
|
||||
],
|
||||
},
|
||||
FileId(
|
||||
2,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "quux",
|
||||
delete: 11..14,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
file_system_edits: [
|
||||
MoveFile {
|
||||
src: FileId(
|
||||
@ -1012,21 +995,18 @@ mod fo$0o;
|
||||
RangeInfo {
|
||||
range: 4..7,
|
||||
info: SourceChange {
|
||||
source_file_edits: [
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 4..7,
|
||||
},
|
||||
],
|
||||
},
|
||||
source_file_edits: {
|
||||
FileId(
|
||||
0,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 4..7,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
file_system_edits: [
|
||||
MoveFile {
|
||||
src: FileId(
|
||||
@ -1062,21 +1042,18 @@ mod outer { mod fo$0o; }
|
||||
RangeInfo {
|
||||
range: 16..19,
|
||||
info: SourceChange {
|
||||
source_file_edits: [
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "bar",
|
||||
delete: 16..19,
|
||||
},
|
||||
],
|
||||
},
|
||||
source_file_edits: {
|
||||
FileId(
|
||||
0,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "bar",
|
||||
delete: 16..19,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
file_system_edits: [
|
||||
MoveFile {
|
||||
src: FileId(
|
||||
@ -1135,34 +1112,28 @@ pub mod foo$0;
|
||||
RangeInfo {
|
||||
range: 8..11,
|
||||
info: SourceChange {
|
||||
source_file_edits: [
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
1,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 8..11,
|
||||
},
|
||||
],
|
||||
},
|
||||
source_file_edits: {
|
||||
FileId(
|
||||
0,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 27..30,
|
||||
},
|
||||
],
|
||||
},
|
||||
SourceFileEdit {
|
||||
file_id: FileId(
|
||||
0,
|
||||
),
|
||||
edit: TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 27..30,
|
||||
},
|
||||
],
|
||||
},
|
||||
FileId(
|
||||
1,
|
||||
): TextEdit {
|
||||
indels: [
|
||||
Indel {
|
||||
insert: "foo2",
|
||||
delete: 8..11,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
file_system_edits: [
|
||||
MoveFile {
|
||||
src: FileId(
|
||||
|
@ -15,8 +15,10 @@
|
||||
|
||||
mod on_enter;
|
||||
|
||||
use ide_db::base_db::{FilePosition, SourceDatabase};
|
||||
use ide_db::{source_change::SourceFileEdit, RootDatabase};
|
||||
use ide_db::{
|
||||
base_db::{FilePosition, SourceDatabase},
|
||||
RootDatabase,
|
||||
};
|
||||
use syntax::{
|
||||
algo::find_node_at_offset,
|
||||
ast::{self, edit::IndentLevel, AstToken},
|
||||
@ -56,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(SourceFileEdit { file_id: 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<TextEdit> {
|
||||
|
@ -3,12 +3,19 @@
|
||||
//!
|
||||
//! 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 stdx::assert_never;
|
||||
use text_edit::TextEdit;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SourceChange {
|
||||
pub source_file_edits: Vec<SourceFileEdit>,
|
||||
pub source_file_edits: FxHashMap<FileId, TextEdit>,
|
||||
pub file_system_edits: Vec<FileSystemEdit>,
|
||||
pub is_snippet: bool,
|
||||
}
|
||||
@ -17,27 +24,50 @@ impl SourceChange {
|
||||
/// Creates a new SourceChange with the given label
|
||||
/// from the edits.
|
||||
pub fn from_edits(
|
||||
source_file_edits: Vec<SourceFileEdit>,
|
||||
source_file_edits: FxHashMap<FileId, TextEdit>,
|
||||
file_system_edits: Vec<FileSystemEdit>,
|
||||
) -> Self {
|
||||
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceFileEdit {
|
||||
pub file_id: FileId,
|
||||
pub edit: TextEdit,
|
||||
}
|
||||
pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
|
||||
SourceChange {
|
||||
source_file_edits: FxHashMap::from_iter(iter::once((file_id, edit))),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SourceFileEdit> for SourceChange {
|
||||
fn from(edit: SourceFileEdit) -> SourceChange {
|
||||
vec![edit].into()
|
||||
pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) {
|
||||
match self.source_file_edits.entry(file_id) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
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 From<Vec<SourceFileEdit>> for SourceChange {
|
||||
fn from(source_file_edits: Vec<SourceFileEdit>) -> SourceChange {
|
||||
impl Extend<(FileId, TextEdit)> for SourceChange {
|
||||
fn extend<T: IntoIterator<Item = (FileId, TextEdit)>>(&mut self, iter: T) {
|
||||
iter.into_iter().for_each(|(file_id, edit)| self.insert_source_edit(file_id, edit));
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
|
||||
fn from(source_file_edits: FxHashMap<FileId, TextEdit>) -> SourceChange {
|
||||
SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
|
||||
}
|
||||
}
|
||||
@ -51,7 +81,7 @@ pub enum FileSystemEdit {
|
||||
impl From<FileSystemEdit> 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,
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> 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 {
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
|
@ -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.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,9 +463,11 @@ 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_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_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))
|
||||
}
|
||||
|
@ -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<lsp_ext::SnippetTextDocumentEdit> {
|
||||
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 {
|
||||
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 =
|
||||
|
@ -75,10 +75,10 @@ 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 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)]
|
||||
@ -159,7 +159,7 @@ impl<'db> MatchFinder<'db> {
|
||||
}
|
||||
|
||||
/// Finds matches for all added rules and returns edits for all found matches.
|
||||
pub fn edits(&self) -> Vec<SourceFileEdit> {
|
||||
pub fn edits(&self) -> FxHashMap<FileId, TextEdit> {
|
||||
use ide_db::base_db::SourceDatabaseExt;
|
||||
let mut matches_by_file = FxHashMap::default();
|
||||
for m in self.matches().matches {
|
||||
@ -169,13 +169,19 @@ 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 });
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
/// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
|
||||
|
@ -810,9 +810,9 @@ mod tests {
|
||||
|
||||
let edits = match_finder.edits();
|
||||
assert_eq!(edits.len(), 1);
|
||||
let edit = &edits[0];
|
||||
let edit = &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); }");
|
||||
}
|
||||
}
|
||||
|
@ -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[&position.file_id].apply(&mut actual);
|
||||
expected.assert_eq(&actual);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user