]> git.lizzy.rs Git - rust.git/blob - src/vertical.rs
Merge pull request #1861 from topecongiro/refactor-chain
[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::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 use rewrite::{Rewrite, RewriteContext};
25 use utils::{contains_skip, mk_sp};
26
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(
32         &self,
33         context: &RewriteContext,
34         shape: Shape,
35         prefix_max_width: usize,
36     ) -> Option<String>;
37 }
38
39 impl AlignedItem for ast::StructField {
40     fn skip(&self) -> bool {
41         contains_skip(&self.attrs)
42     }
43
44     fn get_span(&self) -> Span {
45         self.span()
46     }
47
48     fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
49         rewrite_struct_field_prefix(context, self, shape)
50     }
51
52     fn rewrite_aligned_item(
53         &self,
54         context: &RewriteContext,
55         shape: Shape,
56         prefix_max_width: usize,
57     ) -> Option<String> {
58         rewrite_struct_field(context, self, shape, prefix_max_width)
59     }
60 }
61
62 impl AlignedItem for ast::Field {
63     fn skip(&self) -> bool {
64         contains_skip(&self.attrs)
65     }
66
67     fn get_span(&self) -> Span {
68         self.span()
69     }
70
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)));
75         };
76         let name = &self.ident.node.to_string();
77         Some(format!("{}{}", attrs_str, name))
78     }
79
80     fn rewrite_aligned_item(
81         &self,
82         context: &RewriteContext,
83         shape: Shape,
84         prefix_max_width: usize,
85     ) -> Option<String> {
86         rewrite_field(context, self, shape, prefix_max_width)
87     }
88 }
89
90 pub fn rewrite_with_alignment<T: AlignedItem>(
91     fields: &[T],
92     context: &RewriteContext,
93     shape: Shape,
94     span: Span,
95     one_line_width: usize,
96 ) -> Option<String> {
97     let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
98         group_aligned_items(context, fields)
99     } else {
100         ("", fields.len() - 1)
101     };
102     let init = &fields[0..group_index + 1];
103     let rest = &fields[group_index + 1..];
104     let init_last_pos = if rest.is_empty() {
105         span.hi
106     } else {
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, ","),
113             missing_span.hi,
114         );
115
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());
119             // 2 = "," + "\n"
120             init_hi + BytePos(offset as u32 + 2)
121         } else if snippet.trim_left().starts_with("/*") {
122             let comment_lines = snippet
123                 .lines()
124                 .position(|line| line.trim_right().ends_with("*/"))
125                 .unwrap_or(0);
126
127             let offset = snippet
128                 .lines()
129                 .take(comment_lines + 1)
130                 .collect::<Vec<_>>()
131                 .join("\n")
132                 .len();
133
134             init_hi + BytePos(offset as u32 + 2)
135         } else {
136             missing_span.lo
137         }
138     };
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(
142         context,
143         init,
144         init_span,
145         shape.indent,
146         one_line_width,
147     ));
148     if rest.is_empty() {
149         Some(result + spaces)
150     } else {
151         let rest_span = mk_sp(init_last_pos, span.hi);
152         let rest_str = try_opt!(rewrite_with_alignment(
153             rest,
154             context,
155             shape,
156             rest_span,
157             one_line_width,
158         ));
159         Some(
160             result + spaces + "\n" +
161                 &shape
162                     .indent
163                     .block_indent(context.config)
164                     .to_string(context.config) + &rest_str,
165         )
166     }
167 }
168
169 fn struct_field_preix_max_min_width<T: AlignedItem>(
170     context: &RewriteContext,
171     fields: &[T],
172     shape: Shape,
173 ) -> (usize, usize) {
174     fields
175         .iter()
176         .map(|field| {
177             field
178                 .rewrite_prefix(context, shape)
179                 .and_then(|field_str| if field_str.contains('\n') {
180                     None
181                 } else {
182                     Some(field_str.len())
183                 })
184         })
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)))
188             }
189             _ => None,
190         })
191         .unwrap_or((0, 0))
192 }
193
194 fn rewrite_aligned_items_inner<T: AlignedItem>(
195     context: &RewriteContext,
196     fields: &[T],
197     span: Span,
198     offset: Indent,
199     one_line_width: usize,
200 ) -> Option<String> {
201     let item_indent = offset.block_indent(context.config);
202     // 1 = ","
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)
208         .unwrap_or(0);
209     if max_diff > context.config.struct_field_align_threshold() {
210         field_prefix_max_width = 0;
211     }
212
213     let items = itemize_list(
214         context.codemap,
215         fields.iter(),
216         "}",
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),
220         span.lo,
221         span.hi,
222         false,
223     ).collect::<Vec<_>>();
224
225     let tactic = definitive_tactic(
226         &items,
227         ListTactic::HorizontalVertical,
228         Separator::Comma,
229         one_line_width,
230     );
231
232     let fmt = ListFormatting {
233         tactic: tactic,
234         separator: ",",
235         trailing_separator: context.config.trailing_comma(),
236         shape: item_shape,
237         ends_with_newline: true,
238         preserve_newline: true,
239         config: context.config,
240     };
241     write_list(&items, &fmt)
242 }
243
244 fn group_aligned_items<T: AlignedItem>(
245     context: &RewriteContext,
246     fields: &[T],
247 ) -> (&'static str, usize) {
248     let mut index = 0;
249     for i in 0..fields.len() - 1 {
250         if fields[i].skip() {
251             return ("", index);
252         }
253         // See if there are comments or empty lines between fields.
254         let span = mk_sp(fields[i].get_span().hi, fields[i + 1].get_span().lo);
255         let snippet = context
256             .snippet(span)
257             .lines()
258             .skip(1)
259             .collect::<Vec<_>>()
260             .join("\n");
261         let spacings = if snippet
262             .lines()
263             .rev()
264             .skip(1)
265             .find(|l| l.trim().is_empty())
266             .is_some()
267         {
268             "\n"
269         } else {
270             ""
271         };
272         if contains_comment(&snippet) || snippet.lines().count() > 1 {
273             return (spacings, index);
274         }
275         index += 1;
276     }
277     ("", index)
278 }