]> git.lizzy.rs Git - rust.git/blob - src/vertical.rs
Cargo fmt
[rust.git] / src / vertical.rs
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.
4 //
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.
10
11 // Format with vertical alignment.
12
13 use std::cmp;
14
15 use syntax::ast;
16 use syntax::codemap::{BytePos, Span};
17
18 use spanned::Spanned;
19 use codemap::SpanUtils;
20 use comment::{combine_strs_with_missing_comments, 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, Separator,
24             SeparatorPlace};
25 use rewrite::{Rewrite, RewriteContext};
26 use shape::{Indent, Shape};
27 use utils::{contains_skip, is_attributes_extendable, mk_sp};
28
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(
34         &self,
35         context: &RewriteContext,
36         shape: Shape,
37         prefix_max_width: usize,
38     ) -> Option<String>;
39 }
40
41 impl AlignedItem for ast::StructField {
42     fn skip(&self) -> bool {
43         contains_skip(&self.attrs)
44     }
45
46     fn get_span(&self) -> Span {
47         self.span()
48     }
49
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())
54         } else {
55             mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
56         };
57         let attrs_extendable = context.config.attributes_on_same_line_as_field()
58             && is_attributes_extendable(&attrs_str);
59         rewrite_struct_field_prefix(context, self).and_then(|field_str| {
60             combine_strs_with_missing_comments(
61                 context,
62                 &attrs_str,
63                 &field_str,
64                 missing_span,
65                 shape,
66                 attrs_extendable,
67             )
68         })
69     }
70
71     fn rewrite_aligned_item(
72         &self,
73         context: &RewriteContext,
74         shape: Shape,
75         prefix_max_width: usize,
76     ) -> Option<String> {
77         rewrite_struct_field(context, self, shape, prefix_max_width)
78     }
79 }
80
81 impl AlignedItem for ast::Field {
82     fn skip(&self) -> bool {
83         contains_skip(&self.attrs)
84     }
85
86     fn get_span(&self) -> Span {
87         self.span()
88     }
89
90     fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
91         let attrs_str = self.attrs.rewrite(context, shape)?;
92         let name = &self.ident.node.to_string();
93         let missing_span = if self.attrs.is_empty() {
94             mk_sp(self.span.lo(), self.span.lo())
95         } else {
96             mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
97         };
98         combine_strs_with_missing_comments(
99             context,
100             &attrs_str,
101             name,
102             missing_span,
103             shape,
104             is_attributes_extendable(&attrs_str),
105         )
106     }
107
108     fn rewrite_aligned_item(
109         &self,
110         context: &RewriteContext,
111         shape: Shape,
112         prefix_max_width: usize,
113     ) -> Option<String> {
114         rewrite_field(context, self, shape, prefix_max_width)
115     }
116 }
117
118 pub fn rewrite_with_alignment<T: AlignedItem>(
119     fields: &[T],
120     context: &RewriteContext,
121     shape: Shape,
122     span: Span,
123     one_line_width: usize,
124 ) -> Option<String> {
125     let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
126         group_aligned_items(context, fields)
127     } else {
128         ("", fields.len() - 1)
129     };
130     let init = &fields[0..group_index + 1];
131     let rest = &fields[group_index + 1..];
132     let init_last_pos = if rest.is_empty() {
133         span.hi()
134     } else {
135         // Decide whether the missing comments should stick to init or rest.
136         let init_hi = init[init.len() - 1].get_span().hi();
137         let rest_lo = rest[0].get_span().lo();
138         let missing_span = mk_sp(init_hi, rest_lo);
139         let missing_span = mk_sp(
140             context.codemap.span_after(missing_span, ","),
141             missing_span.hi(),
142         );
143
144         let snippet = context.snippet(missing_span);
145         if snippet.trim_left().starts_with("//") {
146             let offset = snippet.lines().next().map_or(0, |l| l.len());
147             // 2 = "," + "\n"
148             init_hi + BytePos(offset as u32 + 2)
149         } else if snippet.trim_left().starts_with("/*") {
150             let comment_lines = snippet
151                 .lines()
152                 .position(|line| line.trim_right().ends_with("*/"))
153                 .unwrap_or(0);
154
155             let offset = snippet
156                 .lines()
157                 .take(comment_lines + 1)
158                 .collect::<Vec<_>>()
159                 .join("\n")
160                 .len();
161
162             init_hi + BytePos(offset as u32 + 2)
163         } else {
164             missing_span.lo()
165         }
166     };
167     let init_span = mk_sp(span.lo(), init_last_pos);
168     let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
169     let result =
170         rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
171     if rest.is_empty() {
172         Some(result + spaces)
173     } else {
174         let rest_span = mk_sp(init_last_pos, span.hi());
175         let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
176         Some(
177             result + spaces + "\n"
178                 + &shape
179                     .indent
180                     .block_indent(context.config)
181                     .to_string(context.config) + &rest_str,
182         )
183     }
184 }
185
186 fn struct_field_prefix_max_min_width<T: AlignedItem>(
187     context: &RewriteContext,
188     fields: &[T],
189     shape: Shape,
190 ) -> (usize, usize) {
191     fields
192         .iter()
193         .map(|field| {
194             field.rewrite_prefix(context, shape).and_then(|field_str| {
195                 if field_str.contains('\n') {
196                     None
197                 } else {
198                     Some(field_str.len())
199                 }
200             })
201         })
202         .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
203             (Some((max_len, min_len)), Some(len)) => {
204                 Some((cmp::max(max_len, len), cmp::min(min_len, len)))
205             }
206             _ => None,
207         })
208         .unwrap_or((0, 0))
209 }
210
211 fn rewrite_aligned_items_inner<T: AlignedItem>(
212     context: &RewriteContext,
213     fields: &[T],
214     span: Span,
215     offset: Indent,
216     one_line_width: usize,
217 ) -> Option<String> {
218     let item_indent = offset.block_indent(context.config);
219     // 1 = ","
220     let item_shape = Shape::indented(item_indent, 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
224         .checked_sub(field_prefix_min_width)
225         .unwrap_or(0);
226     if max_diff > context.config.struct_field_align_threshold() {
227         field_prefix_max_width = 0;
228     }
229
230     let items = itemize_list(
231         context.codemap,
232         fields.iter(),
233         "}",
234         |field| field.get_span().lo(),
235         |field| field.get_span().hi(),
236         |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
237         span.lo(),
238         span.hi(),
239         false,
240     ).collect::<Vec<_>>();
241
242     let tactic = definitive_tactic(
243         &items,
244         ListTactic::HorizontalVertical,
245         Separator::Comma,
246         one_line_width,
247     );
248
249     let fmt = ListFormatting {
250         tactic: tactic,
251         separator: ",",
252         trailing_separator: context.config.trailing_comma(),
253         separator_place: SeparatorPlace::Back,
254         shape: item_shape,
255         ends_with_newline: true,
256         preserve_newline: true,
257         config: context.config,
258     };
259     write_list(&items, &fmt)
260 }
261
262 fn group_aligned_items<T: AlignedItem>(
263     context: &RewriteContext,
264     fields: &[T],
265 ) -> (&'static str, usize) {
266     let mut index = 0;
267     for i in 0..fields.len() - 1 {
268         if fields[i].skip() {
269             return ("", index);
270         }
271         // See if there are comments or empty lines between fields.
272         let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
273         let snippet = context
274             .snippet(span)
275             .lines()
276             .skip(1)
277             .collect::<Vec<_>>()
278             .join("\n");
279         let spacings = if snippet.lines().rev().skip(1).any(|l| l.trim().is_empty()) {
280             "\n"
281         } else {
282             ""
283         };
284         if contains_comment(&snippet) || snippet.lines().count() > 1 {
285             return (spacings, index);
286         }
287         index += 1;
288     }
289     ("", index)
290 }