From e010b144d5abcbd0947d0490123ef693a6a17c78 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 30 Sep 2019 09:27:26 +0300 Subject: move field list to ast/edit.rs --- crates/ra_syntax/src/algo.rs | 17 ++++++++- crates/ra_syntax/src/ast/edit.rs | 82 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) (limited to 'crates/ra_syntax') diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 46680a08f..f33d2ad4e 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs @@ -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 { After(T), } +pub struct TreeDiff { + replacements: FxHashMap, +} + +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 { /// /// 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 { +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, diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 2af6f573e..6e64c0675 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -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 { + self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) + } +} + pub fn strip_attrs_and_docs(node: N) -> N { N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap() } -- cgit v1.2.3