From 97e92b35cc13c7afac277451db8997a2bf69678c Mon Sep 17 00:00:00 2001
From: Nick Cameron <ncameron@mozilla.com>
Date: Wed, 2 Sep 2015 14:11:19 +1200
Subject: [PATCH] Preserve some whitespace between struct fields etc.

---
 src/items.rs            |  4 +--
 src/lists.rs            | 66 +++++++++++++++++++++++++++++------------
 tests/source/structs.rs | 28 +++++++++++++++++
 tests/target/structs.rs | 18 +++++++++++
 4 files changed, 94 insertions(+), 22 deletions(-)

diff --git a/src/items.rs b/src/items.rs
index 4d84a142045..36728ce84fa 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -632,13 +632,11 @@ impl<'a> FmtVisitor<'a> {
         let break_line = !is_tuple || generics_str.contains('\n') ||
                          single_line_cost as usize + used_budget > self.config.max_width;
 
-        if break_line {
+        let tactic = if break_line {
             let indentation = make_indent(offset + self.config.tab_spaces);
             result.push('\n');
             result.push_str(&indentation);
-        }
 
-        let tactic = if break_line {
             ListTactic::Vertical
         } else {
             ListTactic::Horizontal
diff --git a/src/lists.rs b/src/lists.rs
index 3b9bb3f4af2..6938e4eb4d4 100644
--- a/src/lists.rs
+++ b/src/lists.rs
@@ -70,9 +70,11 @@ impl<'a> ListFormatting<'a> {
 
 pub struct ListItem {
     pub pre_comment: Option<String>,
-    // Item should include attributes and doc comments
+    // Item should include attributes and doc comments.
     pub item: String,
     pub post_comment: Option<String>,
+    // Whether there is extra whitespace before this item.
+    pub new_lines: bool,
 }
 
 impl ListItem {
@@ -86,7 +88,7 @@ impl ListItem {
     }
 
     pub fn from_str<S: Into<String>>(s: S) -> ListItem {
-        ListItem { pre_comment: None, item: s.into(), post_comment: None }
+        ListItem { pre_comment: None, item: s.into(), post_comment: None, new_lines: false }
     }
 }
 
@@ -206,10 +208,8 @@ pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> St
 
         // Post-comments
         if tactic != ListTactic::Vertical && item.post_comment.is_some() {
-            let formatted_comment = rewrite_comment(item.post_comment.as_ref().unwrap(),
-                                                    true,
-                                                    formatting.v_width,
-                                                    0);
+            let comment = item.post_comment.as_ref().unwrap();
+            let formatted_comment = rewrite_comment(comment, true, formatting.v_width, 0);
 
             result.push(' ');
             result.push_str(&formatted_comment);
@@ -234,6 +234,10 @@ pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> St
             result.push(' ');
             result.push_str(&formatted_comment);
         }
+
+        if !last && tactic == ListTactic::Vertical && item.new_lines {
+            result.push('\n');
+        }
     }
 
     result
@@ -264,13 +268,14 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
         let white_space: &[_] = &[' ', '\t'];
 
         self.inner.next().map(|item| {
+            let mut new_lines = false;
             // Pre-comment
             let pre_snippet = self.codemap.span_to_snippet(codemap::mk_sp(self.prev_span_end,
                                                                           (self.get_lo)(&item)))
                                           .unwrap();
-            let pre_snippet = pre_snippet.trim();
-            let pre_comment = if !pre_snippet.is_empty() {
-                Some(pre_snippet.to_owned())
+            let trimmed_pre_snippet = pre_snippet.trim();
+            let pre_comment = if !trimmed_pre_snippet.is_empty() {
+                Some(trimmed_pre_snippet.to_owned())
             } else {
                 None
             };
@@ -307,7 +312,7 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
                                      separator_index + 1)
                         }
                         // Potential *single* line comment.
-                        (_, Some(j)) => { j + 1 }
+                        (_, Some(j)) => j + 1,
                         _ => post_snippet.len()
                     }
                 },
@@ -317,18 +322,40 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
                 }
             };
 
-            // Cleanup post-comment: strip separators and whitespace.
-            self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
-            let mut post_snippet = post_snippet[..comment_end].trim();
+            if !post_snippet.is_empty() && comment_end > 0 {
+                // Account for extra whitespace between items. This is fiddly
+                // because of the way we divide pre- and post- comments.
 
-            if post_snippet.starts_with(',') {
-                post_snippet = post_snippet[1..].trim_matches(white_space);
-            } else if post_snippet.ends_with(",") {
-                post_snippet = post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space);
+                // Everything from the separator to the next item.
+                let test_snippet = &post_snippet[comment_end-1..];
+                let first_newline = test_snippet.find('\n').unwrap_or(test_snippet.len());
+                // From the end of the first line of comments.
+                let test_snippet = &test_snippet[first_newline..];
+                let first = test_snippet.find(|c: char| !c.is_whitespace())
+                                        .unwrap_or(test_snippet.len());
+                // From the end of the first line of comments to the next non-whitespace char.
+                let test_snippet = &test_snippet[..first];
+
+                if test_snippet.chars().filter(|c| c == &'\n').count() > 1 {
+                    // There were multiple line breaks which got trimmed to nothing.
+                    new_lines = true;
+                }
             }
 
-            let post_comment = if !post_snippet.is_empty() {
-                Some(post_snippet.to_owned())
+            // Cleanup post-comment: strip separators and whitespace.
+            self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
+            let post_snippet = post_snippet[..comment_end].trim();
+
+            let post_snippet_trimmed = if post_snippet.starts_with(',') {
+                post_snippet[1..].trim_matches(white_space)
+            } else if post_snippet.ends_with(",") {
+                post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
+            } else {
+                post_snippet
+            };
+
+            let post_comment = if !post_snippet_trimmed.is_empty() {
+                Some(post_snippet_trimmed.to_owned())
             } else {
                 None
             };
@@ -337,6 +364,7 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
                 pre_comment: pre_comment,
                 item: (self.get_item_string)(&item),
                 post_comment: post_comment,
+                new_lines: new_lines,
             }
         })
     }
diff --git a/tests/source/structs.rs b/tests/source/structs.rs
index 68c7ff0df3c..76602a7a561 100644
--- a/tests/source/structs.rs
+++ b/tests/source/structs.rs
@@ -49,15 +49,43 @@ pub struct Foo<'a, Y: Baz>
 }
 
 struct Baz {
+
     a: A,  // Comment A
     b: B, // Comment B
     c: C,   // Comment C
+
+}
+
+struct Baz {
+    a: A,  // Comment A
+
+    b: B, // Comment B
+
+
+
+
+    c: C,   // Comment C
+}
+
+struct Baz {
+
+    a: A,
+
+    b: B,
+    c: C,
+
+
+
+    
+    d: D
+
 }
 
 struct Baz
 {
     // Comment A
     a: A,
+    
     // Comment B
 b: B,
     // Comment C
diff --git a/tests/target/structs.rs b/tests/target/structs.rs
index 1f48c66b6a4..31e32a51b42 100644
--- a/tests/target/structs.rs
+++ b/tests/target/structs.rs
@@ -54,9 +54,27 @@ struct Baz {
     c: C, // Comment C
 }
 
+struct Baz {
+    a: A, // Comment A
+
+    b: B, // Comment B
+
+    c: C, // Comment C
+}
+
+struct Baz {
+    a: A,
+
+    b: B,
+    c: C,
+
+    d: D,
+}
+
 struct Baz {
     // Comment A
     a: A,
+
     // Comment B
     b: B,
     // Comment C