// 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 or the MIT license // , 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::{combine_strs_with_missing_comments, 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, Separator, SeparatorPlace}; use rewrite::{Rewrite, RewriteContext}; use utils::{contains_skip, is_attributes_extendable, mk_sp}; pub trait AlignedItem { fn skip(&self) -> bool; fn get_span(&self) -> Span; fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option; fn rewrite_aligned_item( &self, context: &RewriteContext, shape: Shape, prefix_max_width: usize, ) -> Option; } 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 { let attrs_str = try_opt!(self.attrs.rewrite(context, shape)); let missing_span = if self.attrs.is_empty() { mk_sp(self.span.lo(), self.span.lo()) } else { mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) }; rewrite_struct_field_prefix(context, self).and_then(|field_str| { combine_strs_with_missing_comments( context, &attrs_str, &field_str, missing_span, shape, is_attributes_extendable(&attrs_str), ) }) } fn rewrite_aligned_item( &self, context: &RewriteContext, shape: Shape, prefix_max_width: usize, ) -> Option { 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 { let attrs_str = try_opt!(self.attrs.rewrite(context, shape)); let name = &self.ident.node.to_string(); let missing_span = if self.attrs.is_empty() { mk_sp(self.span.lo(), self.span.lo()) } else { mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) }; combine_strs_with_missing_comments( context, &attrs_str, name, missing_span, shape, is_attributes_extendable(&attrs_str), ) } fn rewrite_aligned_item( &self, context: &RewriteContext, shape: Shape, prefix_max_width: usize, ) -> Option { rewrite_field(context, self, shape, prefix_max_width) } } pub fn rewrite_with_alignment( fields: &[T], context: &RewriteContext, shape: Shape, span: Span, one_line_width: usize, ) -> Option { 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::>() .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( 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( context: &RewriteContext, fields: &[T], span: Span, offset: Indent, one_line_width: usize, ) -> Option { 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(), false, ).collect::>(); let tactic = definitive_tactic( &items, ListTactic::HorizontalVertical, Separator::Comma, one_line_width, ); let fmt = ListFormatting { tactic: tactic, separator: ",", trailing_separator: context.config.trailing_comma(), separator_place: SeparatorPlace::Back, shape: item_shape, ends_with_newline: true, preserve_newline: true, config: context.config, }; write_list(&items, &fmt) } fn group_aligned_items( 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::>() .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) }