2017-07-03 18:54:41 +09:00
|
|
|
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
// Format with vertical alignment.
|
|
|
|
|
|
|
|
use std::cmp;
|
|
|
|
|
2017-07-13 18:42:14 +09:00
|
|
|
use syntax::ast;
|
2018-08-23 17:10:46 -04:00
|
|
|
use syntax::source_map::{BytePos, Span};
|
2017-07-13 18:42:14 +09:00
|
|
|
|
2019-02-04 13:30:43 +03:00
|
|
|
use crate::comment::{combine_strs_with_missing_comments, contains_comment};
|
|
|
|
use crate::config::lists::*;
|
|
|
|
use crate::expr::rewrite_field;
|
|
|
|
use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix};
|
|
|
|
use crate::lists::{
|
|
|
|
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
|
|
|
|
};
|
|
|
|
use crate::rewrite::{Rewrite, RewriteContext};
|
|
|
|
use crate::shape::{Indent, Shape};
|
|
|
|
use crate::source_map::SpanUtils;
|
|
|
|
use crate::spanned::Spanned;
|
|
|
|
use crate::utils::{contains_skip, is_attributes_extendable, mk_sp, rewrite_ident};
|
2017-07-03 18:54:41 +09:00
|
|
|
|
|
|
|
pub trait AlignedItem {
|
|
|
|
fn skip(&self) -> bool;
|
|
|
|
fn get_span(&self) -> Span;
|
|
|
|
fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String>;
|
|
|
|
fn rewrite_aligned_item(
|
|
|
|
&self,
|
|
|
|
context: &RewriteContext,
|
|
|
|
shape: Shape,
|
|
|
|
prefix_max_width: usize,
|
|
|
|
) -> Option<String>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AlignedItem for ast::StructField {
|
|
|
|
fn skip(&self) -> bool {
|
|
|
|
contains_skip(&self.attrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_span(&self) -> Span {
|
|
|
|
self.span()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
|
2017-10-05 20:50:19 +09:00
|
|
|
let attrs_str = self.attrs.rewrite(context, shape)?;
|
2017-08-11 17:52:49 +09:00
|
|
|
let missing_span = if self.attrs.is_empty() {
|
2017-08-19 21:47:40 +03:00
|
|
|
mk_sp(self.span.lo(), self.span.lo())
|
2017-08-11 17:52:49 +09:00
|
|
|
} else {
|
2017-08-19 21:47:40 +03:00
|
|
|
mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
|
2017-08-11 17:52:49 +09:00
|
|
|
};
|
2018-01-11 16:55:50 +09:00
|
|
|
let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
|
2017-08-11 17:52:49 +09:00
|
|
|
rewrite_struct_field_prefix(context, self).and_then(|field_str| {
|
|
|
|
combine_strs_with_missing_comments(
|
|
|
|
context,
|
|
|
|
&attrs_str,
|
|
|
|
&field_str,
|
|
|
|
missing_span,
|
|
|
|
shape,
|
2017-09-04 16:15:32 +09:00
|
|
|
attrs_extendable,
|
2017-08-11 17:52:49 +09:00
|
|
|
)
|
|
|
|
})
|
2017-07-03 18:54:41 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_aligned_item(
|
|
|
|
&self,
|
|
|
|
context: &RewriteContext,
|
|
|
|
shape: Shape,
|
|
|
|
prefix_max_width: usize,
|
|
|
|
) -> Option<String> {
|
|
|
|
rewrite_struct_field(context, self, shape, prefix_max_width)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AlignedItem for ast::Field {
|
|
|
|
fn skip(&self) -> bool {
|
|
|
|
contains_skip(&self.attrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_span(&self) -> Span {
|
|
|
|
self.span()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
|
2017-10-05 20:50:19 +09:00
|
|
|
let attrs_str = self.attrs.rewrite(context, shape)?;
|
2018-06-25 23:36:45 +09:00
|
|
|
let name = rewrite_ident(context, self.ident);
|
2017-08-11 17:52:49 +09:00
|
|
|
let missing_span = if self.attrs.is_empty() {
|
2017-08-19 21:47:40 +03:00
|
|
|
mk_sp(self.span.lo(), self.span.lo())
|
2017-08-11 17:52:49 +09:00
|
|
|
} else {
|
2017-08-19 21:47:40 +03:00
|
|
|
mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
|
2017-08-11 17:52:49 +09:00
|
|
|
};
|
|
|
|
combine_strs_with_missing_comments(
|
|
|
|
context,
|
|
|
|
&attrs_str,
|
|
|
|
name,
|
|
|
|
missing_span,
|
|
|
|
shape,
|
|
|
|
is_attributes_extendable(&attrs_str),
|
|
|
|
)
|
2017-07-03 18:54:41 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_aligned_item(
|
|
|
|
&self,
|
|
|
|
context: &RewriteContext,
|
|
|
|
shape: Shape,
|
|
|
|
prefix_max_width: usize,
|
|
|
|
) -> Option<String> {
|
|
|
|
rewrite_field(context, self, shape, prefix_max_width)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rewrite_with_alignment<T: AlignedItem>(
|
|
|
|
fields: &[T],
|
|
|
|
context: &RewriteContext,
|
|
|
|
shape: Shape,
|
|
|
|
span: Span,
|
|
|
|
one_line_width: usize,
|
|
|
|
) -> Option<String> {
|
|
|
|
let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
|
|
|
|
group_aligned_items(context, fields)
|
|
|
|
} else {
|
|
|
|
("", fields.len() - 1)
|
|
|
|
};
|
2018-09-01 16:26:47 -04:00
|
|
|
let init = &fields[0..=group_index];
|
2017-07-03 18:54:41 +09:00
|
|
|
let rest = &fields[group_index + 1..];
|
|
|
|
let init_last_pos = if rest.is_empty() {
|
2017-08-19 21:47:40 +03:00
|
|
|
span.hi()
|
2017-07-03 18:54:41 +09:00
|
|
|
} else {
|
|
|
|
// Decide whether the missing comments should stick to init or rest.
|
2017-08-19 21:47:40 +03:00
|
|
|
let init_hi = init[init.len() - 1].get_span().hi();
|
|
|
|
let rest_lo = rest[0].get_span().lo();
|
2017-07-03 18:54:41 +09:00
|
|
|
let missing_span = mk_sp(init_hi, rest_lo);
|
|
|
|
let missing_span = mk_sp(
|
2018-02-19 12:41:43 +09:00
|
|
|
context.snippet_provider.span_after(missing_span, ","),
|
2017-08-19 21:47:40 +03:00
|
|
|
missing_span.hi(),
|
2017-07-03 18:54:41 +09:00
|
|
|
);
|
|
|
|
|
|
|
|
let snippet = context.snippet(missing_span);
|
2018-12-18 03:21:31 +01:00
|
|
|
if snippet.trim_start().starts_with("//") {
|
2017-07-03 18:54:41 +09:00
|
|
|
let offset = snippet.lines().next().map_or(0, |l| l.len());
|
|
|
|
// 2 = "," + "\n"
|
|
|
|
init_hi + BytePos(offset as u32 + 2)
|
2018-12-18 03:21:31 +01:00
|
|
|
} else if snippet.trim_start().starts_with("/*") {
|
2017-07-03 18:54:41 +09:00
|
|
|
let comment_lines = snippet
|
|
|
|
.lines()
|
2018-12-18 03:21:31 +01:00
|
|
|
.position(|line| line.trim_end().ends_with("*/"))
|
2017-07-03 18:54:41 +09:00
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
let offset = snippet
|
|
|
|
.lines()
|
|
|
|
.take(comment_lines + 1)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("\n")
|
|
|
|
.len();
|
|
|
|
|
|
|
|
init_hi + BytePos(offset as u32 + 2)
|
|
|
|
} else {
|
2017-08-19 21:47:40 +03:00
|
|
|
missing_span.lo()
|
2017-07-03 18:54:41 +09:00
|
|
|
}
|
|
|
|
};
|
2017-08-19 21:47:40 +03:00
|
|
|
let init_span = mk_sp(span.lo(), init_last_pos);
|
2017-07-03 18:54:41 +09:00
|
|
|
let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
|
2017-10-05 20:50:19 +09:00
|
|
|
let result =
|
|
|
|
rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
|
2017-07-03 18:54:41 +09:00
|
|
|
if rest.is_empty() {
|
|
|
|
Some(result + spaces)
|
|
|
|
} else {
|
2017-08-19 21:47:40 +03:00
|
|
|
let rest_span = mk_sp(init_last_pos, span.hi());
|
2017-10-05 20:50:19 +09:00
|
|
|
let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
|
2018-11-04 10:51:38 +01:00
|
|
|
Some(format!(
|
|
|
|
"{}{}\n{}{}",
|
|
|
|
result,
|
|
|
|
spaces,
|
|
|
|
&shape.indent.to_string(context.config),
|
|
|
|
&rest_str
|
|
|
|
))
|
2017-07-03 18:54:41 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 07:33:55 +01:00
|
|
|
fn struct_field_prefix_max_min_width<T: AlignedItem>(
|
2017-07-03 18:54:41 +09:00
|
|
|
context: &RewriteContext,
|
|
|
|
fields: &[T],
|
|
|
|
shape: Shape,
|
|
|
|
) -> (usize, usize) {
|
|
|
|
fields
|
|
|
|
.iter()
|
|
|
|
.map(|field| {
|
2017-11-02 21:45:00 +09:00
|
|
|
field.rewrite_prefix(context, shape).and_then(|field_str| {
|
|
|
|
if field_str.contains('\n') {
|
2017-07-03 18:54:41 +09:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(field_str.len())
|
2017-11-02 21:45:00 +09:00
|
|
|
}
|
|
|
|
})
|
2018-09-19 23:22:26 +09:00
|
|
|
})
|
|
|
|
.fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
|
2017-07-03 18:54:41 +09:00
|
|
|
(Some((max_len, min_len)), Some(len)) => {
|
|
|
|
Some((cmp::max(max_len, len), cmp::min(min_len, len)))
|
|
|
|
}
|
|
|
|
_ => None,
|
2018-09-19 23:22:26 +09:00
|
|
|
})
|
|
|
|
.unwrap_or((0, 0))
|
2017-07-03 18:54:41 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_aligned_items_inner<T: AlignedItem>(
|
|
|
|
context: &RewriteContext,
|
|
|
|
fields: &[T],
|
|
|
|
span: Span,
|
|
|
|
offset: Indent,
|
|
|
|
one_line_width: usize,
|
|
|
|
) -> Option<String> {
|
|
|
|
// 1 = ","
|
2018-11-04 10:51:38 +01:00
|
|
|
let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
|
2017-07-03 18:54:41 +09:00
|
|
|
let (mut field_prefix_max_width, field_prefix_min_width) =
|
2017-11-01 07:33:55 +01:00
|
|
|
struct_field_prefix_max_min_width(context, fields, item_shape);
|
2018-05-14 21:58:57 +09:00
|
|
|
let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width);
|
2017-07-03 18:54:41 +09:00
|
|
|
if max_diff > context.config.struct_field_align_threshold() {
|
|
|
|
field_prefix_max_width = 0;
|
|
|
|
}
|
|
|
|
|
2018-10-12 20:41:56 +02:00
|
|
|
let mut items = itemize_list(
|
2018-02-19 12:41:43 +09:00
|
|
|
context.snippet_provider,
|
2017-07-03 18:54:41 +09:00
|
|
|
fields.iter(),
|
|
|
|
"}",
|
2017-11-16 17:38:12 +09:00
|
|
|
",",
|
2017-08-19 21:47:40 +03:00
|
|
|
|field| field.get_span().lo(),
|
|
|
|
|field| field.get_span().hi(),
|
2017-07-03 18:54:41 +09:00
|
|
|
|field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
|
2017-08-19 21:47:40 +03:00
|
|
|
span.lo(),
|
|
|
|
span.hi(),
|
2017-08-07 17:29:55 +09:00
|
|
|
false,
|
2018-09-19 23:22:26 +09:00
|
|
|
)
|
|
|
|
.collect::<Vec<_>>();
|
2017-07-03 18:54:41 +09:00
|
|
|
|
2017-07-31 16:23:42 +09:00
|
|
|
let tactic = definitive_tactic(
|
|
|
|
&items,
|
|
|
|
ListTactic::HorizontalVertical,
|
|
|
|
Separator::Comma,
|
|
|
|
one_line_width,
|
|
|
|
);
|
2017-07-03 18:54:41 +09:00
|
|
|
|
2018-10-12 20:41:56 +02:00
|
|
|
if tactic == DefinitiveListTactic::Horizontal {
|
|
|
|
// since the items fits on a line, there is no need to align them
|
|
|
|
let do_rewrite =
|
|
|
|
|field: &T| -> Option<String> { field.rewrite_aligned_item(context, item_shape, 0) };
|
|
|
|
fields
|
|
|
|
.iter()
|
|
|
|
.zip(items.iter_mut())
|
|
|
|
.for_each(|(field, list_item): (&T, &mut ListItem)| {
|
|
|
|
if list_item.item.is_some() {
|
|
|
|
list_item.item = do_rewrite(field);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-03 22:13:20 +09:00
|
|
|
let fmt = ListFormatting::new(item_shape, context.config)
|
|
|
|
.tactic(tactic)
|
|
|
|
.trailing_separator(context.config.trailing_comma())
|
|
|
|
.preserve_newline(true);
|
2017-07-03 18:54:41 +09:00
|
|
|
write_list(&items, &fmt)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn group_aligned_items<T: AlignedItem>(
|
|
|
|
context: &RewriteContext,
|
|
|
|
fields: &[T],
|
|
|
|
) -> (&'static str, usize) {
|
|
|
|
let mut index = 0;
|
|
|
|
for i in 0..fields.len() - 1 {
|
|
|
|
if fields[i].skip() {
|
|
|
|
return ("", index);
|
|
|
|
}
|
|
|
|
// See if there are comments or empty lines between fields.
|
2017-08-19 21:47:40 +03:00
|
|
|
let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
|
2017-07-03 18:54:41 +09:00
|
|
|
let snippet = context
|
|
|
|
.snippet(span)
|
|
|
|
.lines()
|
|
|
|
.skip(1)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("\n");
|
2017-08-29 22:16:04 +09:00
|
|
|
let spacings = if snippet.lines().rev().skip(1).any(|l| l.trim().is_empty()) {
|
2017-07-03 18:54:41 +09:00
|
|
|
"\n"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
if contains_comment(&snippet) || snippet.lines().count() > 1 {
|
|
|
|
return (spacings, index);
|
|
|
|
}
|
|
|
|
index += 1;
|
|
|
|
}
|
|
|
|
("", index)
|
|
|
|
}
|