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.
17 use syntax::source_map::{BytePos, Span};
19 use comment::{combine_strs_with_missing_comments, contains_comment};
20 use expr::rewrite_field;
21 use items::{rewrite_struct_field, rewrite_struct_field_prefix};
22 use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator};
23 use rewrite::{Rewrite, RewriteContext};
24 use shape::{Indent, Shape};
25 use source_map::SpanUtils;
27 use utils::{contains_skip, is_attributes_extendable, mk_sp, rewrite_ident};
29 pub trait AlignedItem {
30 fn skip(&self) -> bool;
31 fn get_span(&self) -> Span;
32 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String>;
33 fn rewrite_aligned_item(
35 context: &RewriteContext,
37 prefix_max_width: usize,
41 impl AlignedItem for ast::StructField {
42 fn skip(&self) -> bool {
43 contains_skip(&self.attrs)
46 fn get_span(&self) -> Span {
50 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
51 let attrs_str = self.attrs.rewrite(context, shape)?;
52 let missing_span = if self.attrs.is_empty() {
53 mk_sp(self.span.lo(), self.span.lo())
55 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
57 let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
58 rewrite_struct_field_prefix(context, self).and_then(|field_str| {
59 combine_strs_with_missing_comments(
70 fn rewrite_aligned_item(
72 context: &RewriteContext,
74 prefix_max_width: usize,
76 rewrite_struct_field(context, self, shape, prefix_max_width)
80 impl AlignedItem for ast::Field {
81 fn skip(&self) -> bool {
82 contains_skip(&self.attrs)
85 fn get_span(&self) -> Span {
89 fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
90 let attrs_str = self.attrs.rewrite(context, shape)?;
91 let name = rewrite_ident(context, self.ident);
92 let missing_span = if self.attrs.is_empty() {
93 mk_sp(self.span.lo(), self.span.lo())
95 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
97 combine_strs_with_missing_comments(
103 is_attributes_extendable(&attrs_str),
107 fn rewrite_aligned_item(
109 context: &RewriteContext,
111 prefix_max_width: usize,
112 ) -> Option<String> {
113 rewrite_field(context, self, shape, prefix_max_width)
117 pub fn rewrite_with_alignment<T: AlignedItem>(
119 context: &RewriteContext,
122 one_line_width: usize,
123 ) -> Option<String> {
124 let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
125 group_aligned_items(context, fields)
127 ("", fields.len() - 1)
129 let init = &fields[0..=group_index];
130 let rest = &fields[group_index + 1..];
131 let init_last_pos = if rest.is_empty() {
134 // Decide whether the missing comments should stick to init or rest.
135 let init_hi = init[init.len() - 1].get_span().hi();
136 let rest_lo = rest[0].get_span().lo();
137 let missing_span = mk_sp(init_hi, rest_lo);
138 let missing_span = mk_sp(
139 context.snippet_provider.span_after(missing_span, ","),
143 let snippet = context.snippet(missing_span);
144 if snippet.trim_start().starts_with("//") {
145 let offset = snippet.lines().next().map_or(0, |l| l.len());
147 init_hi + BytePos(offset as u32 + 2)
148 } else if snippet.trim_start().starts_with("/*") {
149 let comment_lines = snippet
151 .position(|line| line.trim_end().ends_with("*/"))
156 .take(comment_lines + 1)
161 init_hi + BytePos(offset as u32 + 2)
166 let init_span = mk_sp(span.lo(), init_last_pos);
167 let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
169 rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
171 Some(result + spaces)
173 let rest_span = mk_sp(init_last_pos, span.hi());
174 let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
179 &shape.indent.to_string(context.config),
185 fn struct_field_prefix_max_min_width<T: AlignedItem>(
186 context: &RewriteContext,
189 ) -> (usize, usize) {
193 field.rewrite_prefix(context, shape).and_then(|field_str| {
194 if field_str.contains('\n') {
197 Some(field_str.len())
201 .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
202 (Some((max_len, min_len)), Some(len)) => {
203 Some((cmp::max(max_len, len), cmp::min(min_len, len)))
210 fn rewrite_aligned_items_inner<T: AlignedItem>(
211 context: &RewriteContext,
215 one_line_width: usize,
216 ) -> Option<String> {
218 let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
219 let (mut field_prefix_max_width, field_prefix_min_width) =
220 struct_field_prefix_max_min_width(context, fields, item_shape);
221 let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width);
222 if max_diff > context.config.struct_field_align_threshold() {
223 field_prefix_max_width = 0;
226 let mut items = itemize_list(
227 context.snippet_provider,
231 |field| field.get_span().lo(),
232 |field| field.get_span().hi(),
233 |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
238 .collect::<Vec<_>>();
240 let tactic = definitive_tactic(
242 ListTactic::HorizontalVertical,
247 if tactic == DefinitiveListTactic::Horizontal {
248 // since the items fits on a line, there is no need to align them
250 |field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
253 .zip(items.iter_mut())
254 .for_each(|(field, list_item): (&T, &mut ListItem)| {
255 if list_item.item.is_some() {
256 list_item.item = do_rewrite(field);
261 let fmt = ListFormatting::new(item_shape, context.config)
263 .trailing_separator(context.config.trailing_comma())
264 .preserve_newline(true);
265 write_list(&items, &fmt)
268 fn group_aligned_items<T: AlignedItem>(
269 context: &RewriteContext,
271 ) -> (&'static str, usize) {
273 for i in 0..fields.len() - 1 {
274 if fields[i].skip() {
277 // See if there are comments or empty lines between fields.
278 let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
279 let snippet = context
285 let spacings = if snippet.lines().rev().skip(1).any(|l| l.trim().is_empty()) {
290 if contains_comment(&snippet) || snippet.lines().count() > 1 {
291 return (spacings, index);