1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // Format with vertical alignment.
16 use syntax::codemap::{BytePos, Span};
18 use {Indent, Shape, Spanned};
19 use codemap::SpanUtils;
20 use comment::contains_comment;
21 use expr::rewrite_field;
22 use items::{rewrite_struct_field, rewrite_struct_field_prefix};
23 use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListTactic};
24 use rewrite::{Rewrite, RewriteContext};
25 use utils::{contains_skip, mk_sp};
27 pub trait AlignedItem {
28 fn skip(&self) -> bool;
29 fn get_span(&self) -> Span;
30 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String>;
31 fn rewrite_aligned_item(
33 context: &RewriteContext,
35 prefix_max_width: usize,
39 impl AlignedItem for ast::StructField {
40 fn skip(&self) -> bool {
41 contains_skip(&self.attrs)
44 fn get_span(&self) -> Span {
48 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
49 rewrite_struct_field_prefix(context, self, shape)
52 fn rewrite_aligned_item(
54 context: &RewriteContext,
56 prefix_max_width: usize,
58 rewrite_struct_field(context, self, shape, prefix_max_width)
62 impl AlignedItem for ast::Field {
63 fn skip(&self) -> bool {
64 contains_skip(&self.attrs)
67 fn get_span(&self) -> Span {
71 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
72 let mut attrs_str = try_opt!(self.attrs.rewrite(context, shape));
73 if !attrs_str.is_empty() {
74 attrs_str.push_str(&format!("\n{}", shape.indent.to_string(context.config)));
76 let name = &self.ident.node.to_string();
77 Some(format!("{}{}", attrs_str, name))
80 fn rewrite_aligned_item(
82 context: &RewriteContext,
84 prefix_max_width: usize,
86 rewrite_field(context, self, shape, prefix_max_width)
90 pub fn rewrite_with_alignment<T: AlignedItem>(
92 context: &RewriteContext,
95 one_line_width: usize,
97 let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
98 group_aligned_items(context, fields)
100 ("", fields.len() - 1)
102 let init = &fields[0..group_index + 1];
103 let rest = &fields[group_index + 1..];
104 let init_last_pos = if rest.is_empty() {
107 // Decide whether the missing comments should stick to init or rest.
108 let init_hi = init[init.len() - 1].get_span().hi;
109 let rest_lo = rest[0].get_span().lo;
110 let missing_span = mk_sp(init_hi, rest_lo);
111 let missing_span = mk_sp(
112 context.codemap.span_after(missing_span, ","),
116 let snippet = context.snippet(missing_span);
117 if snippet.trim_left().starts_with("//") {
118 let offset = snippet.lines().next().map_or(0, |l| l.len());
120 init_hi + BytePos(offset as u32 + 2)
121 } else if snippet.trim_left().starts_with("/*") {
122 let comment_lines = snippet
124 .position(|line| line.trim_right().ends_with("*/"))
129 .take(comment_lines + 1)
134 init_hi + BytePos(offset as u32 + 2)
139 let init_span = mk_sp(span.lo, init_last_pos);
140 let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
141 let result = try_opt!(rewrite_aligned_items_inner(
149 Some(result + spaces)
151 let rest_span = mk_sp(init_last_pos, span.hi);
152 let rest_str = try_opt!(rewrite_with_alignment(
160 result + spaces + "\n" +
163 .block_indent(context.config)
164 .to_string(context.config) + &rest_str,
169 fn struct_field_preix_max_min_width<T: AlignedItem>(
170 context: &RewriteContext,
173 ) -> (usize, usize) {
177 field.rewrite_prefix(context, shape).and_then(
178 |field_str| if field_str.contains('\n') {
181 Some(field_str.len())
185 .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
186 (Some((max_len, min_len)), Some(len)) => {
187 Some((cmp::max(max_len, len), cmp::min(min_len, len)))
194 fn rewrite_aligned_items_inner<T: AlignedItem>(
195 context: &RewriteContext,
199 one_line_width: usize,
200 ) -> Option<String> {
201 let item_indent = offset.block_indent(context.config);
203 let item_shape = try_opt!(Shape::indented(item_indent, context.config).sub_width(1));
204 let (mut field_prefix_max_width, field_prefix_min_width) =
205 struct_field_preix_max_min_width(context, fields, item_shape);
206 let max_diff = field_prefix_max_width
207 .checked_sub(field_prefix_min_width)
209 if max_diff > context.config.struct_field_align_threshold() {
210 field_prefix_max_width = 0;
213 let items = itemize_list(
217 |field| field.get_span().lo,
218 |field| field.get_span().hi,
219 |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
222 ).collect::<Vec<_>>();
224 let tactic = definitive_tactic(&items, ListTactic::HorizontalVertical, one_line_width);
226 let fmt = ListFormatting {
229 trailing_separator: context.config.trailing_comma(),
231 ends_with_newline: true,
232 config: context.config,
234 write_list(&items, &fmt)
237 fn group_aligned_items<T: AlignedItem>(
238 context: &RewriteContext,
240 ) -> (&'static str, usize) {
242 for i in 0..fields.len() - 1 {
243 if fields[i].skip() {
246 // See if there are comments or empty lines between fields.
247 let span = mk_sp(fields[i].get_span().hi, fields[i + 1].get_span().lo);
248 let snippet = context
254 let spacings = if snippet
258 .find(|l| l.trim().is_empty())
265 if contains_comment(&snippet) || snippet.lines().count() > 1 {
266 return (spacings, index);