]> git.lizzy.rs Git - rust.git/commitdiff
Add struct_field_align_threshold for vertical alignment
authortopecongiro <seuchida@gmail.com>
Mon, 3 Jul 2017 09:54:41 +0000 (18:54 +0900)
committertopecongiro <seuchida@gmail.com>
Mon, 3 Jul 2017 09:54:41 +0000 (18:54 +0900)
Configurations.md
src/config.rs
src/expr.rs
src/items.rs
src/lib.rs
src/vertical.rs [new file with mode: 0644]

index a436e5159c5b23a9c4ce597fc6778a55fc2110fa..5de757a91685998ab6d0fff1c6ee1311308cdb8f 100644 (file)
@@ -1209,6 +1209,33 @@ fn lorem<T: Eq>(t: T) {
 
 See also: [`space_before_bound`](#space_before_bound).
 
+## `struct_field_align_threshold`
+
+The maximum diff of width between struct fields to be aligned with each other.
+
+- **Default value** : 0
+- **Possible values**: any positive integer
+
+#### `0`:
+
+```rust
+struct Foo {
+    x: u32,
+    yy: u32,
+    zzz: u32,
+}
+```
+
+#### `20`:
+
+```rust
+struct Foo {
+    x:   u32,
+    yy:  u32,
+    zzz: u32,
+}
+```
+
 ## `space_after_struct_lit_field_colon`
 
 Leave a space after the colon in a struct literal field
index 153fad178cc1462a5c8c086f5b617f25b8627b34..bc00fd176cf0a97f0f34a48b042f1587e518f9b4 100644 (file)
@@ -598,7 +598,9 @@ pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
         "What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage";
     condense_wildcard_suffixes: bool, false, "Replace strings of _ wildcards by a single .. in \
                                               tuple patterns";
-    combine_control_expr: bool, true, "Combine control expressions with funciton calls."
+    combine_control_expr: bool, true, "Combine control expressions with funciton calls.";
+    struct_field_align_threshold: usize, 0, "Align struct fields if their diffs fits within \
+                                             threshold."
 }
 
 #[cfg(test)]
index e6b6c53fa2e41fea44ca62fa7364f5351d59b836..d9d20b1092cbdcc80db6005084a36c9e515fc6a7 100644 (file)
@@ -30,6 +30,7 @@
 use chains::rewrite_chain;
 use macros::{rewrite_macro, MacroPosition};
 use patterns::{TuplePatField, can_be_overflowed_pat};
+use vertical::rewrite_with_alignment;
 
 use syntax::{ast, ptr};
 use syntax::codemap::{CodeMap, Span, BytePos};
@@ -2528,6 +2529,14 @@ fn rewrite_index(
     ))
 }
 
+fn struct_lit_can_be_aligned(fields: &[ast::Field], base: &Option<&ast::Expr>) -> bool {
+    if base.is_some() {
+        return false;
+    }
+
+    fields.iter().all(|field| !field.is_shorthand)
+}
+
 fn rewrite_struct_lit<'a>(
     context: &RewriteContext,
     path: &ast::Path,
@@ -2557,50 +2566,71 @@ enum StructLitField<'a> {
         return Some(format!("{} {{}}", path_str));
     }
 
-    let field_iter = fields
-        .into_iter()
-        .map(StructLitField::Regular)
-        .chain(base.into_iter().map(StructLitField::Base));
-
     // Foo { a: Foo } - indent is +3, width is -5.
     let (h_shape, v_shape) = try_opt!(struct_lit_shape(shape, context, path_str.len() + 3, 2));
 
-    let span_lo = |item: &StructLitField| match *item {
-        StructLitField::Regular(field) => field.span.lo,
-        StructLitField::Base(expr) => {
-            let last_field_hi = fields.last().map_or(span.lo, |field| field.span.hi);
-            let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo));
-            let pos = snippet.find_uncommented("..").unwrap();
-            last_field_hi + BytePos(pos as u32)
-        }
-    };
-    let span_hi = |item: &StructLitField| match *item {
-        StructLitField::Regular(field) => field.span.hi,
-        StructLitField::Base(expr) => expr.span.hi,
-    };
-    let rewrite = |item: &StructLitField| match *item {
-        StructLitField::Regular(field) => {
-            // The 1 taken from the v_budget is for the comma.
-            rewrite_field(context, field, try_opt!(v_shape.sub_width(1)))
-        }
-        StructLitField::Base(expr) => {
-            // 2 = ..
-            expr.rewrite(context, try_opt!(v_shape.shrink_left(2)))
-                .map(|s| format!("..{}", s))
-        }
+    let one_line_width = h_shape.map_or(0, |shape| shape.width);
+    let body_lo = context.codemap.span_after(span, "{");
+    let fields_str = if struct_lit_can_be_aligned(fields, &base) &&
+        context.config.struct_field_align_threshold() > 0
+    {
+        try_opt!(rewrite_with_alignment(
+            fields,
+            context,
+            shape,
+            mk_sp(body_lo, span.hi),
+            one_line_width,
+        ))
+    } else {
+        let field_iter = fields
+            .into_iter()
+            .map(StructLitField::Regular)
+            .chain(base.into_iter().map(StructLitField::Base));
+
+        let span_lo = |item: &StructLitField| match *item {
+            StructLitField::Regular(field) => field.span().lo,
+            StructLitField::Base(expr) => {
+                let last_field_hi = fields.last().map_or(span.lo, |field| field.span.hi);
+                let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo));
+                let pos = snippet.find_uncommented("..").unwrap();
+                last_field_hi + BytePos(pos as u32)
+            }
+        };
+        let span_hi = |item: &StructLitField| match *item {
+            StructLitField::Regular(field) => field.span().hi,
+            StructLitField::Base(expr) => expr.span.hi,
+        };
+        let rewrite = |item: &StructLitField| match *item {
+            StructLitField::Regular(field) => {
+                // The 1 taken from the v_budget is for the comma.
+                rewrite_field(context, field, try_opt!(v_shape.sub_width(1)), 0)
+            }
+            StructLitField::Base(expr) => {
+                // 2 = ..
+                expr.rewrite(context, try_opt!(v_shape.shrink_left(2)))
+                    .map(|s| format!("..{}", s))
+            }
+        };
+
+        let items = itemize_list(
+            context.codemap,
+            field_iter,
+            "}",
+            span_lo,
+            span_hi,
+            rewrite,
+            body_lo,
+            span.hi,
+        );
+        let item_vec = items.collect::<Vec<_>>();
+
+        let tactic = struct_lit_tactic(h_shape, context, &item_vec);
+        let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
+        let fmt = struct_lit_formatting(nested_shape, tactic, context, base.is_some());
+
+        try_opt!(write_list(&item_vec, &fmt))
     };
 
-    let items = itemize_list(
-        context.codemap,
-        field_iter,
-        "}",
-        span_lo,
-        span_hi,
-        rewrite,
-        context.codemap.span_after(span, "{"),
-        span.hi,
-    );
-    let item_vec = items.collect::<Vec<_>>();
     let fields_str = wrap_struct_field(context, &fields_str, shape, v_shape, one_line_width);
     Some(format!("{} {{{}}}", path_str, fields_str))
 
@@ -2639,18 +2669,32 @@ pub fn struct_lit_field_separator(config: &Config) -> &str {
     )
 }
 
-fn rewrite_field(context: &RewriteContext, field: &ast::Field, shape: Shape) -> Option<String> {
+pub fn rewrite_field(
+    context: &RewriteContext,
+    field: &ast::Field,
+    shape: Shape,
+    prefix_max_width: usize,
+) -> Option<String> {
+    if contains_skip(&field.attrs) {
+        return wrap_str(
+            context.snippet(field.span()),
+            context.config.max_width(),
+            shape,
+        );
+    }
     let name = &field.ident.node.to_string();
     if field.is_shorthand {
         Some(name.to_string())
     } else {
-        let separator = struct_lit_field_separator(context.config);
+        let mut separator = String::from(struct_lit_field_separator(context.config));
+        for _ in 0..prefix_max_width.checked_sub(name.len()).unwrap_or(0) {
+            separator.push(' ');
+        }
         let overhead = name.len() + separator.len();
-        let mut expr_shape = try_opt!(shape.sub_width(overhead));
-        expr_shape.offset += overhead;
+        let expr_shape = try_opt!(shape.offset_left(overhead));
         let expr = field.expr.rewrite(context, expr_shape);
 
-        let mut attrs_str = try_opt!((*field.attrs).rewrite(context, shape));
+        let mut attrs_str = try_opt!(field.attrs.rewrite(context, shape));
         if !attrs_str.is_empty() {
             attrs_str.push_str(&format!("\n{}", shape.indent.to_string(context.config)));
         };
index cb68231733ecb0ddaa7410eecb874b3552e8f003..0e8c10ea1815cefbfa08bd0c584c4544dfdb7292 100644 (file)
@@ -23,6 +23,7 @@
 use rewrite::{Rewrite, RewriteContext};
 use config::{Config, IndentStyle, Density, ReturnIndent, BraceStyle, Style};
 use types::join_bounds;
+use vertical::rewrite_with_alignment;
 
 use syntax::{ast, abi, ptr, symbol};
 use syntax::codemap::{Span, BytePos};
@@ -1100,49 +1101,14 @@ fn format_struct_struct(
         return Some(result);
     }
 
-    let item_indent = offset.block_indent(context.config);
-    // 1 = ","
-    let item_budget = try_opt!(
-        context
-            .config
-            .max_width()
-            .checked_sub(item_indent.width() + 1)
-    );
-
-    let items = itemize_list(
-        context.codemap,
-        fields.iter(),
-        "}",
-        |field| {
-            // Include attributes and doc comments, if present
-            if !field.attrs.is_empty() {
-                field.attrs[0].span.lo
-            } else {
-                field.span.lo
-            }
-        },
-        |field| field.ty.span.hi,
-        |field| field.rewrite(context, Shape::legacy(item_budget, item_indent)),
-        context.codemap.span_after(span, "{"),
-        span.hi,
-    ).collect::<Vec<_>>();
-    // 1 = ,
-    let budget = context.config.max_width() - offset.width() + context.config.tab_spaces() - 1;
-
-    let tactic = match one_line_width {
-        Some(w) => definitive_tactic(&items, ListTactic::LimitedHorizontalVertical(w), budget),
-        None => DefinitiveListTactic::Vertical,
-    };
+    let items_str = try_opt!(rewrite_with_alignment(
+        fields,
+        context,
+        Shape::indented(offset, context.config),
+        mk_sp(body_lo, span.hi),
+        one_line_width.unwrap_or(0),
+    ));
 
-    let fmt = ListFormatting {
-        tactic: tactic,
-        separator: ",",
-        trailing_separator: context.config.trailing_comma(),
-        shape: Shape::legacy(budget, item_indent),
-        ends_with_newline: true,
-        config: context.config,
-    };
-    let items_str = try_opt!(write_list(&items, &fmt));
     if one_line_width.is_some() && !items_str.contains('\n') {
         Some(format!("{} {} }}", result, items_str))
     } else {
@@ -1257,7 +1223,9 @@ fn format_tuple_struct(
                 }
             },
             |field| field.ty.span.hi,
-            |field| field.rewrite(context, Shape::legacy(item_budget, item_indent)),
+            |field| {
+                rewrite_struct_field(context, field, Shape::legacy(item_budget, item_indent), 0)
+            },
             context.codemap.span_after(span, "("),
             span.hi,
         );
@@ -1430,97 +1398,125 @@ fn rewrite_missing_comment_on_field(
     }
 }
 
-impl Rewrite for ast::StructField {
-    fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
-        if contains_skip(&self.attrs) {
-            let span = context.snippet(mk_sp(self.attrs[0].span.lo, self.span.hi));
-            return wrap_str(span, context.config.max_width(), shape);
+pub fn rewrite_struct_field_prefix(
+    context: &RewriteContext,
+    field: &ast::StructField,
+    shape: Shape,
+) -> Option<String> {
+    let vis = format_visibility(&field.vis);
+    let mut attr_str = try_opt!(
+        field
+            .attrs
+            .rewrite(context, Shape::indented(shape.indent, context.config))
+    );
+    // Try format missing comments after attributes
+    let missing_comment = if !field.attrs.is_empty() {
+        rewrite_missing_comment_on_field(
+            context,
+            shape,
+            field.attrs[field.attrs.len() - 1].span.hi,
+            field.span.lo,
+            &mut attr_str,
+        ).unwrap_or(String::new())
+    } else {
+        String::new()
+    };
+
+    let type_annotation_spacing = type_annotation_spacing(context.config);
+    Some(match field.ident {
+        Some(name) => {
+            format!(
+                "{}{}{}{}{}:",
+                attr_str,
+                missing_comment,
+                vis,
+                name,
+                type_annotation_spacing.0
+            )
         }
+        None => format!("{}{}{}", attr_str, missing_comment, vis),
+    })
+}
 
-        let name = self.ident;
-        let vis = format_visibility(&self.vis);
-        let mut attr_str = try_opt!(
-            self.attrs
-                .rewrite(context, Shape::indented(shape.indent, context.config))
-        );
-        // Try format missing comments after attributes
-        let missing_comment = if !self.attrs.is_empty() {
-            rewrite_missing_comment_on_field(
-                context,
-                shape,
-                self.attrs[self.attrs.len() - 1].span.hi,
-                self.span.lo,
-                &mut attr_str,
-            ).unwrap_or(String::new())
-        } else {
-            String::new()
-        };
+fn rewrite_struct_field_type(
+    context: &RewriteContext,
+    last_line_width: usize,
+    field: &ast::StructField,
+    spacing: &str,
+    shape: Shape,
+) -> Option<String> {
+    let ty_shape = try_opt!(shape.offset_left(last_line_width + spacing.len()));
+    field
+        .ty
+        .rewrite(context, ty_shape)
+        .map(|ty| format!("{}{}", spacing, ty))
+}
 
-        let type_annotation_spacing = type_annotation_spacing(context.config);
-        let mut result = match name {
-            Some(name) => {
-                format!(
-                    "{}{}{}{}{}:",
-                    attr_str,
-                    missing_comment,
-                    vis,
-                    name,
-                    type_annotation_spacing.0
-                )
-            }
-            None => format!("{}{}{}", attr_str, missing_comment, vis),
-        };
 
-        let type_offset = shape.indent.block_indent(context.config);
-        let rewrite_type_in_next_line = || {
-            self.ty
-                .rewrite(context, Shape::indented(type_offset, context.config))
-        };
+pub fn rewrite_struct_field(
+    context: &RewriteContext,
+    field: &ast::StructField,
+    shape: Shape,
+    lhs_max_width: usize,
+) -> Option<String> {
+    if contains_skip(&field.attrs) {
+        let span = context.snippet(mk_sp(field.attrs[0].span.lo, field.span.hi));
+        return wrap_str(span, context.config.max_width(), shape);
+    }
 
-        let last_line_width = last_line_width(&result) + type_annotation_spacing.1.len();
-        let budget = try_opt!(shape.width.checked_sub(last_line_width));
-        let ty_rewritten = self.ty.rewrite(
-            context,
-            Shape::legacy(budget, shape.indent + last_line_width),
-        );
-        match ty_rewritten {
-            Some(ref ty) if ty.contains('\n') => {
-                let new_ty = rewrite_type_in_next_line();
-                match new_ty {
-                    Some(ref new_ty)
-                        if !new_ty.contains('\n') &&
-                               new_ty.len() + type_offset.width() <= context.config.max_width() => {
-                        Some(format!(
-                            "{}\n{}{}",
-                            result,
-                            type_offset.to_string(&context.config),
-                            &new_ty
-                        ))
-                    }
-                    _ => {
-                        if name.is_some() {
-                            result.push_str(type_annotation_spacing.1);
-                        }
-                        Some(result + &ty)
-                    }
-                }
-            }
-            Some(ty) => {
-                if name.is_some() {
-                    result.push_str(type_annotation_spacing.1);
+    let type_annotation_spacing = type_annotation_spacing(context.config);
+    let prefix = try_opt!(rewrite_struct_field_prefix(context, field, shape));
+
+    // Try to put everything on a single line.
+    let last_line_width = last_line_width(&prefix);
+    let mut spacing = String::from(if field.ident.is_some() {
+        type_annotation_spacing.1
+    } else {
+        ""
+    });
+    let lhs_offset = lhs_max_width.checked_sub(last_line_width).unwrap_or(0);
+    for _ in 0..lhs_offset {
+        spacing.push(' ');
+    }
+    let ty_rewritten = rewrite_struct_field_type(context, last_line_width, field, &spacing, shape);
+    if let Some(ref ty) = ty_rewritten {
+        if !ty.contains('\n') {
+            return Some(prefix + &ty);
+        }
+    }
+
+    // We must use multiline.
+    let type_offset = shape.indent.block_indent(context.config);
+    let rewrite_type_in_next_line = || {
+        field
+            .ty
+            .rewrite(context, Shape::indented(type_offset, context.config))
+    };
+
+    match ty_rewritten {
+        // If we start from the next line and type fits in a single line, then do so.
+        Some(ref ty) => {
+            match rewrite_type_in_next_line() {
+                Some(ref new_ty) if !new_ty.contains('\n') => {
+                    Some(format!(
+                        "{}\n{}{}",
+                        prefix,
+                        type_offset.to_string(&context.config),
+                        &new_ty
+                    ))
                 }
-                Some(result + &ty)
-            }
-            None => {
-                let ty = try_opt!(rewrite_type_in_next_line());
-                Some(format!(
-                    "{}\n{}{}",
-                    result,
-                    type_offset.to_string(&context.config),
-                    &ty
-                ))
+                _ => Some(prefix + &ty),
             }
         }
+        _ => {
+            let ty = try_opt!(rewrite_type_in_next_line());
+            Some(format!(
+                "{}\n{}{}",
+                prefix,
+                type_offset.to_string(&context.config),
+                &ty
+            ))
+        }
     }
 }
 
index 5e512b4fa89ff0f6d6818db338f2fc2e2acf7028..f0c3ebf49a1d0248b9d3b1cd17ec75efc3fa0acd 100644 (file)
@@ -76,6 +76,7 @@
 mod macros;
 mod patterns;
 mod summary;
+mod vertical;
 
 const MIN_STRING: usize = 10;
 // When we get scoped annotations, we should have rustfmt::skip.
diff --git a/src/vertical.rs b/src/vertical.rs
new file mode 100644 (file)
index 0000000..d6b3444
--- /dev/null
@@ -0,0 +1,271 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Format with vertical alignment.
+
+use std::cmp;
+
+use {Indent, Shape, Spanned};
+use codemap::SpanUtils;
+use comment::contains_comment;
+use expr::rewrite_field;
+use items::{rewrite_struct_field, rewrite_struct_field_prefix};
+use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListTactic};
+use rewrite::{Rewrite, RewriteContext};
+use utils::{contains_skip, mk_sp};
+
+use syntax::ast;
+use syntax::codemap::{Span, BytePos};
+
+pub trait AlignedItem {
+    fn skip(&self) -> bool;
+    fn get_span(&self) -> Span;
+    fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String>;
+    fn rewrite_aligned_item(
+        &self,
+        context: &RewriteContext,
+        shape: Shape,
+        prefix_max_width: usize,
+    ) -> Option<String>;
+}
+
+impl AlignedItem for ast::StructField {
+    fn skip(&self) -> bool {
+        contains_skip(&self.attrs)
+    }
+
+    fn get_span(&self) -> Span {
+        self.span()
+    }
+
+    fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+        rewrite_struct_field_prefix(context, self, shape)
+    }
+
+    fn rewrite_aligned_item(
+        &self,
+        context: &RewriteContext,
+        shape: Shape,
+        prefix_max_width: usize,
+    ) -> Option<String> {
+        rewrite_struct_field(context, self, shape, prefix_max_width)
+    }
+}
+
+impl AlignedItem for ast::Field {
+    fn skip(&self) -> bool {
+        contains_skip(&self.attrs)
+    }
+
+    fn get_span(&self) -> Span {
+        self.span()
+    }
+
+    fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+        let mut attrs_str = try_opt!(self.attrs.rewrite(context, shape));
+        if !attrs_str.is_empty() {
+            attrs_str.push_str(&format!("\n{}", shape.indent.to_string(context.config)));
+        };
+        let name = &self.ident.node.to_string();
+        Some(format!("{}{}", attrs_str, name))
+    }
+
+    fn rewrite_aligned_item(
+        &self,
+        context: &RewriteContext,
+        shape: Shape,
+        prefix_max_width: usize,
+    ) -> Option<String> {
+        rewrite_field(context, self, shape, prefix_max_width)
+    }
+}
+
+pub fn rewrite_with_alignment<T: AlignedItem>(
+    fields: &[T],
+    context: &RewriteContext,
+    shape: Shape,
+    span: Span,
+    one_line_width: usize,
+) -> Option<String> {
+    let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
+        group_aligned_items(context, fields)
+    } else {
+        ("", fields.len() - 1)
+    };
+    let init = &fields[0..group_index + 1];
+    let rest = &fields[group_index + 1..];
+    let init_last_pos = if rest.is_empty() {
+        span.hi
+    } else {
+        // Decide whether the missing comments should stick to init or rest.
+        let init_hi = init[init.len() - 1].get_span().hi;
+        let rest_lo = rest[0].get_span().lo;
+        let missing_span = mk_sp(init_hi, rest_lo);
+        let missing_span = mk_sp(
+            context.codemap.span_after(missing_span, ","),
+            missing_span.hi,
+        );
+
+        let snippet = context.snippet(missing_span);
+        if snippet.trim_left().starts_with("//") {
+            let offset = snippet.lines().next().map_or(0, |l| l.len());
+            // 2 = "," + "\n"
+            init_hi + BytePos(offset as u32 + 2)
+        } else if snippet.trim_left().starts_with("/*") {
+            let comment_lines = snippet
+                .lines()
+                .position(|line| line.trim_right().ends_with("*/"))
+                .unwrap_or(0);
+
+            let offset = snippet
+                .lines()
+                .take(comment_lines + 1)
+                .collect::<Vec<_>>()
+                .join("\n")
+                .len();
+
+            init_hi + BytePos(offset as u32 + 2)
+        } else {
+            missing_span.lo
+        }
+    };
+    let init_span = mk_sp(span.lo, init_last_pos);
+    let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
+    let result = try_opt!(rewrite_aligned_items_inner(
+        context,
+        init,
+        init_span,
+        shape.indent,
+        one_line_width,
+    ));
+    if rest.is_empty() {
+        Some(result + spaces)
+    } else {
+        let rest_span = mk_sp(init_last_pos, span.hi);
+        let rest_str = try_opt!(rewrite_with_alignment(
+            rest,
+            context,
+            shape,
+            rest_span,
+            one_line_width,
+        ));
+        Some(
+            result + spaces + "\n" +
+                &shape
+                    .indent
+                    .block_indent(context.config)
+                    .to_string(context.config) + &rest_str,
+        )
+    }
+}
+
+fn struct_field_preix_max_min_width<T: AlignedItem>(
+    context: &RewriteContext,
+    fields: &[T],
+    shape: Shape,
+) -> (usize, usize) {
+    fields
+        .iter()
+        .map(|field| {
+            field.rewrite_prefix(context, shape).and_then(
+                |field_str| if field_str.contains('\n') {
+                    None
+                } else {
+                    Some(field_str.len())
+                },
+            )
+        })
+        .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
+            (Some((max_len, min_len)), Some(len)) => {
+                Some((cmp::max(max_len, len), cmp::min(min_len, len)))
+            }
+            _ => None,
+        })
+        .unwrap_or((0, 0))
+}
+
+fn rewrite_aligned_items_inner<T: AlignedItem>(
+    context: &RewriteContext,
+    fields: &[T],
+    span: Span,
+    offset: Indent,
+    one_line_width: usize,
+) -> Option<String> {
+    let item_indent = offset.block_indent(context.config);
+    // 1 = ","
+    let item_shape = try_opt!(Shape::indented(item_indent, context.config).sub_width(1));
+    let (mut field_prefix_max_width, field_prefix_min_width) =
+        struct_field_preix_max_min_width(context, fields, item_shape);
+    let max_diff = field_prefix_max_width
+        .checked_sub(field_prefix_min_width)
+        .unwrap_or(0);
+    if max_diff > context.config.struct_field_align_threshold() {
+        field_prefix_max_width = 0;
+    }
+
+    let items = itemize_list(
+        context.codemap,
+        fields.iter(),
+        "}",
+        |field| field.get_span().lo,
+        |field| field.get_span().hi,
+        |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
+        span.lo,
+        span.hi,
+    ).collect::<Vec<_>>();
+
+    let tactic = definitive_tactic(&items, ListTactic::HorizontalVertical, one_line_width);
+
+    let fmt = ListFormatting {
+        tactic: tactic,
+        separator: ",",
+        trailing_separator: context.config.trailing_comma(),
+        shape: item_shape,
+        ends_with_newline: true,
+        config: context.config,
+    };
+    write_list(&items, &fmt)
+}
+
+fn group_aligned_items<T: AlignedItem>(
+    context: &RewriteContext,
+    fields: &[T],
+) -> (&'static str, usize) {
+    let mut index = 0;
+    for i in 0..fields.len() - 1 {
+        if fields[i].skip() {
+            return ("", index);
+        }
+        // See if there are comments or empty lines between fields.
+        let span = mk_sp(fields[i].get_span().hi, fields[i + 1].get_span().lo);
+        let snippet = context
+            .snippet(span)
+            .lines()
+            .skip(1)
+            .collect::<Vec<_>>()
+            .join("\n");
+        let spacings = if snippet
+            .lines()
+            .rev()
+            .skip(1)
+            .find(|l| l.trim().is_empty())
+            .is_some()
+        {
+            "\n"
+        } else {
+            ""
+        };
+        if contains_comment(&snippet) || snippet.lines().count() > 1 {
+            return (spacings, index);
+        }
+        index += 1;
+    }
+    ("", index)
+}