};
use ide_db::{
label::Label,
- source_change::{FileSystemEdit, SourceChange, SourceFileEdits},
+ source_change::{FileSystemEdit, SourceChange},
RootDatabase,
};
use syntax::{
pub(crate) struct AssistBuilder {
edit: TextEditBuilder,
file_id: FileId,
- is_snippet: bool,
- source_file_edits: SourceFileEdits,
- 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: 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) {
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);
}
}
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.
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) {
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)
}
}
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
&& 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 {
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);
}
}
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::{
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,
))),
);
.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
Fix {
label: "Create module",
source_change: SourceChange {
- source_file_edits: SourceFileEdits {
- edits: {},
- },
+ source_file_edits: {},
file_system_edits: [
CreateFile {
dst: AnchoredPathBuf {
//! 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;
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,
)),
),
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,
)),
));
};
use ide_db::{
base_db::{AnchoredPathBuf, FileId},
- source_change::{FileSystemEdit, SourceFileEdits},
+ source_change::{FileSystemEdit, SourceChange},
RootDatabase,
};
use syntax::{
};
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,
))
}
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))
}
.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))
}
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<ast::RecordFieldList> {
match field_def_list {
label::Label,
line_index::{LineCol, LineIndex},
search::SearchScope,
- source_change::{FileSystemEdit, SourceChange, SourceFileEdits},
+ source_change::{FileSystemEdit, SourceChange},
symbol_index::Query,
RootDatabase,
};
use crate::{
FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
- SourceFileEdits, TextRange, TextSize,
+ TextRange, TextSize,
};
type RenameResult<T> = Result<T, RenameError>;
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);
};
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(..) => {}
}
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(
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(
.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();
} 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(
(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)]
Ok(source_change) => {
let mut text_edit_builder = TextEdit::builder();
let mut file_id: Option<FileId> = 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);
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: [
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: [
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: [
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: [
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: [
use ide_db::{
base_db::{FilePosition, SourceDatabase},
- source_change::SourceFileEdits,
RootDatabase,
};
use syntax::{
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<TextEdit> {
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<FileId, TextEdit>,
pub file_system_edits: Vec<FileSystemEdit>,
pub is_snippet: bool,
}
/// Creates a new SourceChange with the given label
/// from the edits.
pub fn from_edits(
- source_file_edits: SourceFileEdits,
+ source_file_edits: FxHashMap<FileId, TextEdit>,
file_system_edits: Vec<FileSystemEdit>,
) -> Self {
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
}
-}
-
-#[derive(Default, Debug, Clone)]
-pub struct SourceFileEdits {
- pub edits: FxHashMap<FileId, TextEdit>,
-}
-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<T: IntoIterator<Item = (FileId, TextEdit)>>(&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<SourceFileEdits> for SourceChange {
- fn from(source_file_edits: SourceFileEdits) -> SourceChange {
+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 }
}
}
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);
};
// 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))
// 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))
}
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));
}
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)]
}
/// Finds matches for all added rules and returns edits for all found matches.
- pub fn edits(&self) -> SourceFileEdits {
+ 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 {
.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
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); }");
// 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);
}