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
"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)]
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};
))
}
+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,
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))
)
}
-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)));
};
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};
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 {
}
},
|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,
);
}
}
-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
+ ))
+ }
}
}
mod macros;
mod patterns;
mod summary;
+mod vertical;
const MIN_STRING: usize = 10;
// When we get scoped annotations, we should have rustfmt::skip.
--- /dev/null
+// 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)
+}