rust/src/vertical.rs

272 lines
7.9 KiB
Rust
Raw Normal View History

// 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;
use syntax::ast;
use syntax::codemap::{BytePos, Span};
use {Indent, Shape, Spanned};
use codemap::SpanUtils;
use comment::contains_comment;
use expr::rewrite_field;
use items::{rewrite_struct_field, rewrite_struct_field_prefix};
use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListTactic};
use rewrite::{Rewrite, RewriteContext};
use utils::{contains_skip, mk_sp};
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> {
rewrite_struct_field_prefix(context, self, shape)
}
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> {
let mut attrs_str = try_opt!(self.attrs.rewrite(context, shape));
if !attrs_str.is_empty() {
attrs_str.push_str(&format!("\n{}", shape.indent.to_string(context.config)));
};
let name = &self.ident.node.to_string();
Some(format!("{}{}", attrs_str, name))
}
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)
};
let init = &fields[0..group_index + 1];
let rest = &fields[group_index + 1..];
let init_last_pos = if rest.is_empty() {
span.hi
} else {
// Decide whether the missing comments should stick to init or rest.
let init_hi = init[init.len() - 1].get_span().hi;
let rest_lo = rest[0].get_span().lo;
let missing_span = mk_sp(init_hi, rest_lo);
let missing_span = mk_sp(
context.codemap.span_after(missing_span, ","),
missing_span.hi,
);
let snippet = context.snippet(missing_span);
if snippet.trim_left().starts_with("//") {
let offset = snippet.lines().next().map_or(0, |l| l.len());
// 2 = "," + "\n"
init_hi + BytePos(offset as u32 + 2)
} else if snippet.trim_left().starts_with("/*") {
let comment_lines = snippet
.lines()
.position(|line| line.trim_right().ends_with("*/"))
.unwrap_or(0);
let offset = snippet
.lines()
.take(comment_lines + 1)
.collect::<Vec<_>>()
.join("\n")
.len();
init_hi + BytePos(offset as u32 + 2)
} else {
missing_span.lo
}
};
let init_span = mk_sp(span.lo, init_last_pos);
let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
let result = try_opt!(rewrite_aligned_items_inner(
context,
init,
init_span,
shape.indent,
one_line_width,
));
if rest.is_empty() {
Some(result + spaces)
} else {
let rest_span = mk_sp(init_last_pos, span.hi);
let rest_str = try_opt!(rewrite_with_alignment(
rest,
context,
shape,
rest_span,
one_line_width,
));
Some(
result + spaces + "\n" +
&shape
.indent
.block_indent(context.config)
.to_string(context.config) + &rest_str,
)
}
}
fn struct_field_preix_max_min_width<T: AlignedItem>(
context: &RewriteContext,
fields: &[T],
shape: Shape,
) -> (usize, usize) {
fields
.iter()
.map(|field| {
field.rewrite_prefix(context, shape).and_then(
|field_str| if field_str.contains('\n') {
None
} else {
Some(field_str.len())
},
)
})
.fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
(Some((max_len, min_len)), Some(len)) => {
Some((cmp::max(max_len, len), cmp::min(min_len, len)))
}
_ => None,
})
.unwrap_or((0, 0))
}
fn rewrite_aligned_items_inner<T: AlignedItem>(
context: &RewriteContext,
fields: &[T],
span: Span,
offset: Indent,
one_line_width: usize,
) -> Option<String> {
let item_indent = offset.block_indent(context.config);
// 1 = ","
let item_shape = try_opt!(Shape::indented(item_indent, context.config).sub_width(1));
let (mut field_prefix_max_width, field_prefix_min_width) =
struct_field_preix_max_min_width(context, fields, item_shape);
let max_diff = field_prefix_max_width
.checked_sub(field_prefix_min_width)
.unwrap_or(0);
if max_diff > context.config.struct_field_align_threshold() {
field_prefix_max_width = 0;
}
let items = itemize_list(
context.codemap,
fields.iter(),
"}",
|field| field.get_span().lo,
|field| field.get_span().hi,
|field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
span.lo,
span.hi,
).collect::<Vec<_>>();
let tactic = definitive_tactic(&items, ListTactic::HorizontalVertical, one_line_width);
let fmt = ListFormatting {
tactic: tactic,
separator: ",",
trailing_separator: context.config.trailing_comma(),
shape: item_shape,
ends_with_newline: true,
config: context.config,
};
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.
let span = mk_sp(fields[i].get_span().hi, fields[i + 1].get_span().lo);
let snippet = context
.snippet(span)
.lines()
.skip(1)
.collect::<Vec<_>>()
.join("\n");
let spacings = if snippet
.lines()
.rev()
.skip(1)
.find(|l| l.trim().is_empty())
.is_some()
{
"\n"
} else {
""
};
if contains_comment(&snippet) || snippet.lines().count() > 1 {
return (spacings, index);
}
index += 1;
}
("", index)
}