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::source_map::{BytePos, Span};
18 use crate::comment::{combine_strs_with_missing_comments, contains_comment};
19 use crate::config::lists::*;
20 use crate::expr::rewrite_field;
21 use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix};
23 definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
25 use crate::rewrite::{Rewrite, RewriteContext};
26 use crate::shape::{Indent, Shape};
27 use crate::source_map::SpanUtils;
28 use crate::spanned::Spanned;
29 use crate::utils::{contains_skip, is_attributes_extendable, mk_sp, rewrite_ident};
31 pub trait AlignedItem {
32 fn skip(&self) -> bool;
33 fn get_span(&self) -> Span;
34 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
35 fn rewrite_aligned_item(
37 context: &RewriteContext<'_>,
39 prefix_max_width: usize,
43 impl AlignedItem for ast::StructField {
44 fn skip(&self) -> bool {
45 contains_skip(&self.attrs)
48 fn get_span(&self) -> Span {
52 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
53 let attrs_str = self.attrs.rewrite(context, shape)?;
54 let missing_span = if self.attrs.is_empty() {
55 mk_sp(self.span.lo(), self.span.lo())
57 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
59 let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
60 rewrite_struct_field_prefix(context, self).and_then(|field_str| {
61 combine_strs_with_missing_comments(
72 fn rewrite_aligned_item(
74 context: &RewriteContext<'_>,
76 prefix_max_width: usize,
78 rewrite_struct_field(context, self, shape, prefix_max_width)
82 impl AlignedItem for ast::Field {
83 fn skip(&self) -> bool {
84 contains_skip(&self.attrs)
87 fn get_span(&self) -> Span {
91 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
92 let attrs_str = self.attrs.rewrite(context, shape)?;
93 let name = rewrite_ident(context, self.ident);
94 let missing_span = if self.attrs.is_empty() {
95 mk_sp(self.span.lo(), self.span.lo())
97 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
99 combine_strs_with_missing_comments(
105 is_attributes_extendable(&attrs_str),
109 fn rewrite_aligned_item(
111 context: &RewriteContext<'_>,
113 prefix_max_width: usize,
114 ) -> Option<String> {
115 rewrite_field(context, self, shape, prefix_max_width)
119 pub fn rewrite_with_alignment<T: AlignedItem>(
121 context: &RewriteContext<'_>,
124 one_line_width: usize,
125 ) -> Option<String> {
126 let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
127 group_aligned_items(context, fields)
129 ("", fields.len() - 1)
131 let init = &fields[0..=group_index];
132 let rest = &fields[group_index + 1..];
133 let init_last_pos = if rest.is_empty() {
136 // Decide whether the missing comments should stick to init or rest.
137 let init_hi = init[init.len() - 1].get_span().hi();
138 let rest_lo = rest[0].get_span().lo();
139 let missing_span = mk_sp(init_hi, rest_lo);
140 let missing_span = mk_sp(
141 context.snippet_provider.span_after(missing_span, ","),
145 let snippet = context.snippet(missing_span);
146 if snippet.trim_start().starts_with("//") {
147 let offset = snippet.lines().next().map_or(0, |l| l.len());
149 init_hi + BytePos(offset as u32 + 2)
150 } else if snippet.trim_start().starts_with("/*") {
151 let comment_lines = snippet
153 .position(|line| line.trim_end().ends_with("*/"))
158 .take(comment_lines + 1)
163 init_hi + BytePos(offset as u32 + 2)
168 let init_span = mk_sp(span.lo(), init_last_pos);
169 let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
171 rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
173 Some(result + spaces)
175 let rest_span = mk_sp(init_last_pos, span.hi());
176 let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
181 &shape.indent.to_string(context.config),
187 fn struct_field_prefix_max_min_width<T: AlignedItem>(
188 context: &RewriteContext<'_>,
191 ) -> (usize, usize) {
195 field.rewrite_prefix(context, shape).and_then(|field_str| {
196 if field_str.contains('\n') {
199 Some(field_str.len())
203 .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
204 (Some((max_len, min_len)), Some(len)) => {
205 Some((cmp::max(max_len, len), cmp::min(min_len, len)))
212 fn rewrite_aligned_items_inner<T: AlignedItem>(
213 context: &RewriteContext<'_>,
217 one_line_width: usize,
218 ) -> Option<String> {
220 let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
221 let (mut field_prefix_max_width, field_prefix_min_width) =
222 struct_field_prefix_max_min_width(context, fields, item_shape);
223 let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width);
224 if max_diff > context.config.struct_field_align_threshold() {
225 field_prefix_max_width = 0;
228 let mut items = itemize_list(
229 context.snippet_provider,
233 |field| field.get_span().lo(),
234 |field| field.get_span().hi(),
235 |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
240 .collect::<Vec<_>>();
242 let tactic = definitive_tactic(
244 ListTactic::HorizontalVertical,
249 if tactic == DefinitiveListTactic::Horizontal {
250 // since the items fits on a line, there is no need to align them
252 |field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
255 .zip(items.iter_mut())
256 .for_each(|(field, list_item): (&T, &mut ListItem)| {
257 if list_item.item.is_some() {
258 list_item.item = do_rewrite(field);
263 let fmt = ListFormatting::new(item_shape, context.config)
265 .trailing_separator(context.config.trailing_comma())
266 .preserve_newline(true);
267 write_list(&items, &fmt)
270 fn group_aligned_items<T: AlignedItem>(
271 context: &RewriteContext<'_>,
273 ) -> (&'static str, usize) {
275 for i in 0..fields.len() - 1 {
276 if fields[i].skip() {
279 // See if there are comments or empty lines between fields.
280 let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
281 let snippet = context
287 let spacings = if snippet.lines().rev().skip(1).any(|l| l.trim().is_empty()) {
292 if contains_comment(&snippet) || snippet.lines().count() > 1 {
293 return (spacings, index);