From e010b144d5abcbd0947d0490123ef693a6a17c78 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
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<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>,
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<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()
 }
-- 
cgit v1.2.3