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