1 // Format with vertical alignment.
5 use itertools::Itertools;
7 use rustc_span::{BytePos, Span};
9 use crate::comment::combine_strs_with_missing_comments;
10 use crate::config::lists::*;
11 use crate::expr::rewrite_field;
12 use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix};
14 definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
16 use crate::rewrite::{Rewrite, RewriteContext};
17 use crate::shape::{Indent, Shape};
18 use crate::source_map::SpanUtils;
19 use crate::spanned::Spanned;
21 contains_skip, is_attributes_extendable, mk_sp, rewrite_ident, trimmed_last_line_width,
24 pub(crate) trait AlignedItem {
25 fn skip(&self) -> bool;
26 fn get_span(&self) -> Span;
27 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
28 fn rewrite_aligned_item(
30 context: &RewriteContext<'_>,
32 prefix_max_width: usize,
36 impl AlignedItem for ast::FieldDef {
37 fn skip(&self) -> bool {
38 contains_skip(&self.attrs)
41 fn get_span(&self) -> Span {
45 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
46 let attrs_str = self.attrs.rewrite(context, shape)?;
47 let missing_span = if self.attrs.is_empty() {
48 mk_sp(self.span.lo(), self.span.lo())
50 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
52 let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
53 rewrite_struct_field_prefix(context, self).and_then(|field_str| {
54 combine_strs_with_missing_comments(
65 fn rewrite_aligned_item(
67 context: &RewriteContext<'_>,
69 prefix_max_width: usize,
71 rewrite_struct_field(context, self, shape, prefix_max_width)
75 impl AlignedItem for ast::ExprField {
76 fn skip(&self) -> bool {
77 contains_skip(&self.attrs)
80 fn get_span(&self) -> Span {
84 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
85 let attrs_str = self.attrs.rewrite(context, shape)?;
86 let name = rewrite_ident(context, self.ident);
87 let missing_span = if self.attrs.is_empty() {
88 mk_sp(self.span.lo(), self.span.lo())
90 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
92 combine_strs_with_missing_comments(
98 is_attributes_extendable(&attrs_str),
102 fn rewrite_aligned_item(
104 context: &RewriteContext<'_>,
106 prefix_max_width: usize,
107 ) -> Option<String> {
108 rewrite_field(context, self, shape, prefix_max_width)
112 pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
114 context: &RewriteContext<'_>,
117 one_line_width: usize,
118 ) -> Option<String> {
119 let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
120 group_aligned_items(context, fields)
122 ("", fields.len() - 1)
124 let init = &fields[0..=group_index];
125 let rest = &fields[group_index + 1..];
126 let init_last_pos = if rest.is_empty() {
129 // Decide whether the missing comments should stick to init or rest.
130 let init_hi = init[init.len() - 1].get_span().hi();
131 let rest_lo = rest[0].get_span().lo();
132 let missing_span = mk_sp(init_hi, rest_lo);
133 let missing_span = mk_sp(
134 context.snippet_provider.span_after(missing_span, ","),
138 let snippet = context.snippet(missing_span);
139 if snippet.trim_start().starts_with("//") {
140 let offset = snippet.lines().next().map_or(0, str::len);
142 init_hi + BytePos(offset as u32 + 2)
143 } else if snippet.trim_start().starts_with("/*") {
144 let comment_lines = snippet
146 .position(|line| line.trim_end().ends_with("*/"))
151 .take(comment_lines + 1)
156 init_hi + BytePos(offset as u32 + 2)
161 let init_span = mk_sp(span.lo(), init_last_pos);
162 let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
164 rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
166 Some(result + spaces)
168 let rest_span = mk_sp(init_last_pos, span.hi());
169 let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
174 &shape.indent.to_string(context.config),
180 fn struct_field_prefix_max_min_width<T: AlignedItem>(
181 context: &RewriteContext<'_>,
184 ) -> (usize, usize) {
189 .rewrite_prefix(context, shape)
190 .map(|field_str| trimmed_last_line_width(&field_str))
192 .fold_options((0, ::std::usize::MAX), |(max_len, min_len), len| {
193 (cmp::max(max_len, len), cmp::min(min_len, len))
198 fn rewrite_aligned_items_inner<T: AlignedItem>(
199 context: &RewriteContext<'_>,
203 one_line_width: usize,
204 ) -> Option<String> {
206 let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
207 let (mut field_prefix_max_width, field_prefix_min_width) =
208 struct_field_prefix_max_min_width(context, fields, item_shape);
209 let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width);
210 if max_diff > context.config.struct_field_align_threshold() {
211 field_prefix_max_width = 0;
214 let mut items = itemize_list(
215 context.snippet_provider,
219 |field| field.get_span().lo(),
220 |field| field.get_span().hi(),
221 |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
226 .collect::<Vec<_>>();
228 let tactic = definitive_tactic(
230 ListTactic::HorizontalVertical,
235 if tactic == DefinitiveListTactic::Horizontal {
236 // since the items fits on a line, there is no need to align them
238 |field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
241 .zip(items.iter_mut())
242 .for_each(|(field, list_item): (&T, &mut ListItem)| {
243 if list_item.item.is_some() {
244 list_item.item = do_rewrite(field);
249 let fmt = ListFormatting::new(item_shape, context.config)
251 .trailing_separator(context.config.trailing_comma())
252 .preserve_newline(true);
253 write_list(&items, &fmt)
256 /// Returns the index in `fields` up to which a field belongs to the current group.
257 /// The returned string is the group separator to use when rewriting the fields.
258 /// Groups are defined by blank lines.
259 fn group_aligned_items<T: AlignedItem>(
260 context: &RewriteContext<'_>,
262 ) -> (&'static str, usize) {
264 for i in 0..fields.len() - 1 {
265 if fields[i].skip() {
268 let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
269 let snippet = context
275 let has_blank_line = snippet
278 .any(|l| l.trim().is_empty());
280 return ("\n", index);