]> git.lizzy.rs Git - rust.git/blob - src/vertical.rs
fe476c0bd40be807780d73e6d475409c3c9d2d32
[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::source_map::{BytePos, Span};
17
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};
22 use crate::lists::{
23     definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
24 };
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};
30
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(
36         &self,
37         context: &RewriteContext<'_>,
38         shape: Shape,
39         prefix_max_width: usize,
40     ) -> Option<String>;
41 }
42
43 impl AlignedItem for ast::StructField {
44     fn skip(&self) -> bool {
45         contains_skip(&self.attrs)
46     }
47
48     fn get_span(&self) -> Span {
49         self.span()
50     }
51
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())
56         } else {
57             mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
58         };
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(
62                 context,
63                 &attrs_str,
64                 &field_str,
65                 missing_span,
66                 shape,
67                 attrs_extendable,
68             )
69         })
70     }
71
72     fn rewrite_aligned_item(
73         &self,
74         context: &RewriteContext<'_>,
75         shape: Shape,
76         prefix_max_width: usize,
77     ) -> Option<String> {
78         rewrite_struct_field(context, self, shape, prefix_max_width)
79     }
80 }
81
82 impl AlignedItem for ast::Field {
83     fn skip(&self) -> bool {
84         contains_skip(&self.attrs)
85     }
86
87     fn get_span(&self) -> Span {
88         self.span()
89     }
90
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())
96         } else {
97             mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
98         };
99         combine_strs_with_missing_comments(
100             context,
101             &attrs_str,
102             name,
103             missing_span,
104             shape,
105             is_attributes_extendable(&attrs_str),
106         )
107     }
108
109     fn rewrite_aligned_item(
110         &self,
111         context: &RewriteContext<'_>,
112         shape: Shape,
113         prefix_max_width: usize,
114     ) -> Option<String> {
115         rewrite_field(context, self, shape, prefix_max_width)
116     }
117 }
118
119 pub fn rewrite_with_alignment<T: AlignedItem>(
120     fields: &[T],
121     context: &RewriteContext<'_>,
122     shape: Shape,
123     span: Span,
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)
128     } else {
129         ("", fields.len() - 1)
130     };
131     let init = &fields[0..=group_index];
132     let rest = &fields[group_index + 1..];
133     let init_last_pos = if rest.is_empty() {
134         span.hi()
135     } else {
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, ","),
142             missing_span.hi(),
143         );
144
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());
148             // 2 = "," + "\n"
149             init_hi + BytePos(offset as u32 + 2)
150         } else if snippet.trim_start().starts_with("/*") {
151             let comment_lines = snippet
152                 .lines()
153                 .position(|line| line.trim_end().ends_with("*/"))
154                 .unwrap_or(0);
155
156             let offset = snippet
157                 .lines()
158                 .take(comment_lines + 1)
159                 .collect::<Vec<_>>()
160                 .join("\n")
161                 .len();
162
163             init_hi + BytePos(offset as u32 + 2)
164         } else {
165             missing_span.lo()
166         }
167     };
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 };
170     let result =
171         rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
172     if rest.is_empty() {
173         Some(result + spaces)
174     } else {
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)?;
177         Some(format!(
178             "{}{}\n{}{}",
179             result,
180             spaces,
181             &shape.indent.to_string(context.config),
182             &rest_str
183         ))
184     }
185 }
186
187 fn struct_field_prefix_max_min_width<T: AlignedItem>(
188     context: &RewriteContext<'_>,
189     fields: &[T],
190     shape: Shape,
191 ) -> (usize, usize) {
192     fields
193         .iter()
194         .map(|field| {
195             field.rewrite_prefix(context, shape).and_then(|field_str| {
196                 if field_str.contains('\n') {
197                     None
198                 } else {
199                     Some(field_str.len())
200                 }
201             })
202         })
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)))
206             }
207             _ => None,
208         })
209         .unwrap_or((0, 0))
210 }
211
212 fn rewrite_aligned_items_inner<T: AlignedItem>(
213     context: &RewriteContext<'_>,
214     fields: &[T],
215     span: Span,
216     offset: Indent,
217     one_line_width: usize,
218 ) -> Option<String> {
219     // 1 = ","
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;
226     }
227
228     let mut items = itemize_list(
229         context.snippet_provider,
230         fields.iter(),
231         "}",
232         ",",
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),
236         span.lo(),
237         span.hi(),
238         false,
239     )
240     .collect::<Vec<_>>();
241
242     let tactic = definitive_tactic(
243         &items,
244         ListTactic::HorizontalVertical,
245         Separator::Comma,
246         one_line_width,
247     );
248
249     if tactic == DefinitiveListTactic::Horizontal {
250         // since the items fits on a line, there is no need to align them
251         let do_rewrite =
252             |field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
253         fields
254             .iter()
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);
259                 }
260             });
261     }
262
263     let fmt = ListFormatting::new(item_shape, context.config)
264         .tactic(tactic)
265         .trailing_separator(context.config.trailing_comma())
266         .preserve_newline(true);
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 }