move field list to ast/edit.rs

This commit is contained in:
Aleksey Kladov
2019-09-30 09:27:26 +03:00
parent 0840ec038b
commit e010b144d5
5 changed files with 106 additions and 108 deletions

View File

@@ -3,6 +3,7 @@ pub mod visit;
use std::ops::RangeInclusive;
use itertools::Itertools;
use ra_text_edit::TextEditBuilder;
use rustc_hash::FxHashMap;
use crate::{
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> {
After(T),
}
pub struct TreeDiff {
replacements: FxHashMap<SyntaxElement, SyntaxElement>,
}
impl TreeDiff {
pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
for (from, to) in self.replacements.iter() {
builder.replace(from.text_range(), to.to_string())
}
}
}
/// Finds minimal the diff, which, applied to `from`, will result in `to`.
///
/// Specifically, returns a map whose keys are descendants of `from` and values
@@ -70,12 +83,12 @@ pub enum InsertPosition<T> {
///
/// A trivial solution is a singletom map `{ from: to }`, but this function
/// tries to find a more fine-grained diff.
pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> {
pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
let mut buf = FxHashMap::default();
// FIXME: this is both horrible inefficient and gives larger than
// necessary diff. I bet there's a cool algorithm to diff trees properly.
go(&mut buf, from.clone().into(), to.clone().into());
return buf;
return TreeDiff { replacements: buf };
fn go(
buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,

View File

@@ -12,7 +12,7 @@ use crate::{
make::{self, tokens},
AstNode,
},
AstToken, InsertPosition, SmolStr, SyntaxElement,
AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode, T,
};
@@ -105,6 +105,86 @@ impl ast::ItemList {
}
}
impl ast::RecordFieldList {
#[must_use]
pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
self.insert_field(InsertPosition::Last, field)
}
#[must_use]
pub fn insert_field(
&self,
position: InsertPosition<&'_ ast::RecordField>,
field: &ast::RecordField,
) -> ast::RecordFieldList {
let is_multiline = self.syntax().text().contains_char('\n');
let ws;
let space = if is_multiline {
ws = tokens::WsBuilder::new(&format!(
"\n{} ",
leading_indent(self.syntax()).unwrap_or("".into())
));
ws.ws()
} else {
tokens::single_space()
};
let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
to_insert.push(space.into());
to_insert.push(field.syntax().clone().into());
to_insert.push(tokens::comma().into());
macro_rules! after_l_curly {
() => {{
let anchor = match self.l_curly() {
Some(it) => it,
None => return self.clone(),
};
InsertPosition::After(anchor)
}};
}
macro_rules! after_field {
($anchor:expr) => {
if let Some(comma) = $anchor
.syntax()
.siblings_with_tokens(Direction::Next)
.find(|it| it.kind() == T![,])
{
InsertPosition::After(comma)
} else {
to_insert.insert(0, tokens::comma().into());
InsertPosition::After($anchor.syntax().clone().into())
}
};
};
let position = match position {
InsertPosition::First => after_l_curly!(),
InsertPosition::Last => {
if !is_multiline {
// don't insert comma before curly
to_insert.pop();
}
match self.fields().last() {
Some(it) => after_field!(it),
None => after_l_curly!(),
}
}
InsertPosition::Before(anchor) => {
InsertPosition::Before(anchor.syntax().clone().into())
}
InsertPosition::After(anchor) => after_field!(anchor),
};
insert_children(self, position, to_insert.iter().cloned())
}
fn l_curly(&self) -> Option<SyntaxElement> {
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
}
}
pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N {
N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
}