use syntax::codemap::{BytePos, CodeMap};
-use {Indent, Shape};
use comment::{find_comment_end, rewrite_comment, FindUncommented};
use config::{Config, IndentStyle};
use rewrite::RewriteContext;
-use utils::{first_line_width, last_line_width, mk_sp};
+use shape::{Indent, Shape};
+use utils::{first_line_width, last_line_width, mk_sp, starts_with_newline};
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
/// Formatting tactic for lists. This will be cast down to a
-/// DefinitiveListTactic depending on the number and length of the items and
+/// `DefinitiveListTactic` depending on the number and length of the items and
/// their comments.
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum ListTactic {
// One item per row.
Vertical,
pub tactic: DefinitiveListTactic,
pub separator: &'a str,
pub trailing_separator: SeparatorTactic,
+ pub separator_place: SeparatorPlace,
pub shape: Shape,
// Non-expressions, e.g. items, will have a new line at the end of the list.
// Important for comment styles.
pub config: &'a Config,
}
+impl<'a> ListFormatting<'a> {
+ pub fn needs_trailing_separator(&self) -> bool {
+ match self.trailing_separator {
+ // We always put separator in front.
+ SeparatorTactic::Always => true,
+ SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical,
+ SeparatorTactic::Never => {
+ self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front()
+ }
+ }
+ }
+}
+
impl AsRef<ListItem> for ListItem {
fn as_ref(&self) -> &ListItem {
self
impl ListItem {
pub fn inner_as_ref(&self) -> &str {
- self.item.as_ref().map_or("", |s| &*s)
+ self.item.as_ref().map_or("", |s| s)
+ }
+
+ pub fn is_different_group(&self) -> bool {
+ self.inner_as_ref().contains('\n') || self.pre_comment.is_some()
+ || self.post_comment
+ .as_ref()
+ .map_or(false, |s| s.contains('\n'))
}
pub fn is_multiline(&self) -> bool {
- self.inner_as_ref().contains('\n') || self.pre_comment.is_some() ||
- self.post_comment
+ self.inner_as_ref().contains('\n')
+ || self.pre_comment
+ .as_ref()
+ .map_or(false, |s| s.contains('\n'))
+ || self.post_comment
.as_ref()
.map_or(false, |s| s.contains('\n'))
}
pub fn has_comment(&self) -> bool {
self.pre_comment
.as_ref()
- .map_or(false, |comment| comment.starts_with("//")) ||
- self.post_comment
+ .map_or(false, |comment| comment.starts_with("//"))
+ || self.post_comment
.as_ref()
.map_or(false, |comment| comment.starts_with("//"))
}
}
}
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
/// The definitive formatting tactic for lists.
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum DefinitiveListTactic {
Vertical,
Horizontal,
}
/// The type of separator for lists.
-#[derive(Eq, PartialEq, Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Separator {
Comma,
VerticalBar,
}
}
+/// Where to put separator.
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
+pub enum SeparatorPlace {
+ Front,
+ Back,
+}
+
+impl_enum_serialize_and_deserialize!(SeparatorPlace, Front, Back);
+
+impl SeparatorPlace {
+ pub fn is_front(&self) -> bool {
+ *self == SeparatorPlace::Front
+ }
+
+ pub fn is_back(&self) -> bool {
+ *self == SeparatorPlace::Back
+ }
+
+ pub fn from_tactic(default: SeparatorPlace, tactic: DefinitiveListTactic) -> SeparatorPlace {
+ match tactic {
+ DefinitiveListTactic::Vertical => default,
+ _ => SeparatorPlace::Back,
+ }
+ }
+}
+
pub fn definitive_tactic<I, T>(
items: I,
tactic: ListTactic,
let total_sep_len = sep.len() * sep_count.checked_sub(1).unwrap_or(0);
let real_total = total_width + total_sep_len;
- if real_total <= limit && !pre_line_comments &&
- !items.into_iter().any(|item| item.as_ref().is_multiline())
+ if real_total <= limit && !pre_line_comments
+ && !items.into_iter().any(|item| item.as_ref().is_multiline())
{
DefinitiveListTactic::Horizontal
} else {
// Now that we know how we will layout, we can decide for sure if there
// will be a trailing separator.
- let mut trailing_separator = needs_trailing_separator(formatting.trailing_separator, tactic);
- let mut result = String::new();
+ let mut trailing_separator = formatting.needs_trailing_separator();
+ let mut result = String::with_capacity(128);
let cloned_items = items.clone();
let mut iter = items.into_iter().enumerate().peekable();
let mut item_max_width: Option<usize> = None;
+ let mut sep_place = SeparatorPlace::from_tactic(formatting.separator_place, tactic);
let mut line_len = 0;
let indent_str = &formatting.shape.indent.to_string(formatting.config);
while let Some((i, item)) = iter.next() {
let item = item.as_ref();
- let inner_item = try_opt!(item.item.as_ref());
+ let inner_item = item.item.as_ref()?;
let first = i == 0;
let last = iter.peek().is_none();
let mut separate = !last || trailing_separator;
inner_item.as_ref()
};
let mut item_last_line_width = item_last_line.len() + item_sep_len;
- if item_last_line.starts_with(indent_str) {
+ if item_last_line.starts_with(&**indent_str) {
item_last_line_width -= indent_str.len();
}
result.push('\n');
result.push_str(indent_str);
line_len = 0;
- if tactic == DefinitiveListTactic::Mixed && formatting.ends_with_newline {
+ if formatting.ends_with_newline {
if last {
separate = true;
} else {
trailing_separator = true;
}
}
+ sep_place = formatting.separator_place;
+ } else {
+ sep_place = SeparatorPlace::Back;
}
if line_len > 0 {
// Block style in non-vertical mode.
let block_mode = tactic != DefinitiveListTactic::Vertical;
// Width restriction is only relevant in vertical mode.
- let comment = try_opt!(rewrite_comment(
- comment,
- block_mode,
- formatting.shape,
- formatting.config,
- ));
+ let comment =
+ rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?;
result.push_str(&comment);
if tactic == DefinitiveListTactic::Vertical {
// We cannot keep pre-comments on the same line if the comment if normalized.
- let keep_comment = if formatting.config.normalize_comments() {
- false
- } else if item.pre_comment_style == ListItemCommentStyle::DifferentLine {
+ let keep_comment = if formatting.config.normalize_comments()
+ || item.pre_comment_style == ListItemCommentStyle::DifferentLine
+ {
false
} else {
// We will try to keep the comment on the same line with the item here.
item_max_width = None;
}
+ if separate && sep_place.is_front() && !first {
+ result.push_str(formatting.separator.trim());
+ result.push(' ');
+ }
result.push_str(&inner_item[..]);
// Post-comments
if tactic != DefinitiveListTactic::Vertical && item.post_comment.is_some() {
let comment = item.post_comment.as_ref().unwrap();
- let formatted_comment = try_opt!(rewrite_comment(
+ let formatted_comment = rewrite_comment(
comment,
true,
Shape::legacy(formatting.shape.width, Indent::empty()),
formatting.config,
- ));
+ )?;
result.push(' ');
result.push_str(&formatted_comment);
}
- if separate {
+ if separate && sep_place.is_back() {
result.push_str(formatting.separator);
}
formatting.config.max_width(),
));
}
- let overhead = if let &mut Some(max_width) = item_max_width {
+ let overhead = if let Some(max_width) = *item_max_width {
max_width + 2
} else {
// 1 = space between item and comment.
let comment_shape = Shape::legacy(width, offset);
// Use block-style only for the last item or multiline comments.
- let block_style = !formatting.ends_with_newline && last ||
- comment.trim().contains('\n') ||
- comment.trim().len() > width;
+ let block_style = !formatting.ends_with_newline && last
+ || comment.trim().contains('\n')
+ || comment.trim().len() > width;
rewrite_comment(comment, block_style, comment_shape, formatting.config)
};
- let mut formatted_comment = try_opt!(rewrite_post_comment(&mut item_max_width));
+ let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
- if !formatted_comment.starts_with('\n') {
+ if !starts_with_newline(&formatted_comment) {
let mut comment_alignment =
post_comment_alignment(item_max_width, inner_item.len());
- if first_line_width(&formatted_comment) + last_line_width(&result) +
- comment_alignment + 1 > formatting.config.max_width()
+ if first_line_width(&formatted_comment) + last_line_width(&result)
+ + comment_alignment + 1 > formatting.config.max_width()
{
item_max_width = None;
- formatted_comment = try_opt!(rewrite_post_comment(&mut item_max_width));
+ formatted_comment = rewrite_post_comment(&mut item_max_width)?;
comment_alignment = post_comment_alignment(item_max_width, inner_item.len());
}
for _ in 0..(comment_alignment + 1) {
result.push(' ');
}
- // An additional space for the missing trailing comma
- if last && item_max_width.is_some() && !separate {
+ // An additional space for the missing trailing separator.
+ if last && item_max_width.is_some() && !separate && !formatting.separator.is_empty()
+ {
result.push(' ');
}
}
item_max_width = None;
}
- if formatting.preserve_newline && !last && tactic == DefinitiveListTactic::Vertical &&
- item.new_lines
+ if formatting.preserve_newline && !last && tactic == DefinitiveListTactic::Vertical
+ && item.new_lines
{
item_max_width = None;
result.push('\n');
for item in items.clone().into_iter().skip(i) {
let item = item.as_ref();
let inner_item_width = item.inner_as_ref().len();
- if !first &&
- (item.is_multiline() || !item.post_comment.is_some() ||
- inner_item_width + overhead > max_budget)
+ if !first
+ && (item.is_different_group() || !item.post_comment.is_some()
+ || inner_item_width + overhead > max_budget)
{
return max_width;
}
prev_span_end: BytePos,
next_span_start: BytePos,
terminator: &'a str,
+ leave_last: bool,
}
impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
.chars()
.rev()
.take(comment_end + 1)
- .find(|c| *c == '\n')
- .is_some()
+ .any(|c| c == '\n')
{
(
Some(trimmed_pre_snippet.to_owned()),
let comment_end = match self.inner.peek() {
Some(..) => {
let mut block_open_index = post_snippet.find("/*");
- // check if it realy is a block comment (and not //*)
+ // check if it really is a block comment (and not //*)
if let Some(i) = block_open_index {
if i > 0 && &post_snippet[i - 1..i] == "/" {
block_open_index = None;
}
}
let newline_index = post_snippet.find('\n');
- let separator_index = post_snippet.find_uncommented(",").unwrap();
-
- match (block_open_index, newline_index) {
- // Separator before comment, with the next item on same line.
- // Comment belongs to next item.
- (Some(i), None) if i > separator_index => separator_index + 1,
- // Block-style post-comment before the separator.
- (Some(i), None) => cmp::max(
- find_comment_end(&post_snippet[i..]).unwrap() + i,
- separator_index + 1,
- ),
- // Block-style post-comment. Either before or after the separator.
- (Some(i), Some(j)) if i < j => cmp::max(
- find_comment_end(&post_snippet[i..]).unwrap() + i,
- separator_index + 1,
- ),
- // Potential *single* line comment.
- (_, Some(j)) if j > separator_index => j + 1,
- _ => post_snippet.len(),
+ if let Some(separator_index) = post_snippet.find_uncommented(",") {
+ match (block_open_index, newline_index) {
+ // Separator before comment, with the next item on same line.
+ // Comment belongs to next item.
+ (Some(i), None) if i > separator_index => separator_index + 1,
+ // Block-style post-comment before the separator.
+ (Some(i), None) => cmp::max(
+ find_comment_end(&post_snippet[i..]).unwrap() + i,
+ separator_index + 1,
+ ),
+ // Block-style post-comment. Either before or after the separator.
+ (Some(i), Some(j)) if i < j => cmp::max(
+ find_comment_end(&post_snippet[i..]).unwrap() + i,
+ separator_index + 1,
+ ),
+ // Potential *single* line comment.
+ (_, Some(j)) if j > separator_index => j + 1,
+ _ => post_snippet.len(),
+ }
+ } else {
+ // Match arms may not have trailing comma. In any case, for match arms,
+ // we will assume that the post comment belongs to the next arm if they
+ // do not end with trailing comma.
+ 1
}
}
None => post_snippet
.find_uncommented(self.terminator)
- .unwrap_or(post_snippet.len()),
+ .unwrap_or_else(|| post_snippet.len()),
};
if !post_snippet.is_empty() && comment_end > 0 {
// Everything from the separator to the next item.
let test_snippet = &post_snippet[comment_end - 1..];
- let first_newline = test_snippet.find('\n').unwrap_or(test_snippet.len());
+ let first_newline = test_snippet
+ .find('\n')
+ .unwrap_or_else(|| test_snippet.len());
// From the end of the first line of comments.
let test_snippet = &test_snippet[first_newline..];
let first = test_snippet
.find(|c: char| !c.is_whitespace())
- .unwrap_or(test_snippet.len());
+ .unwrap_or_else(|| test_snippet.len());
// From the end of the first line of comments to the next non-whitespace char.
let test_snippet = &test_snippet[..first];
self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
let post_snippet = post_snippet[..comment_end].trim();
- let post_snippet_trimmed = if post_snippet.starts_with(',') {
+ let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
post_snippet[1..].trim_matches(white_space)
} else if post_snippet.ends_with(',') {
post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
ListItem {
pre_comment: pre_comment,
pre_comment_style: pre_comment_style,
- item: (self.get_item_string)(&item),
+ item: if self.inner.peek().is_none() && self.leave_last {
+ None
+ } else {
+ (self.get_item_string)(&item)
+ },
post_comment: post_comment,
new_lines: new_lines,
}
get_item_string: F3,
prev_span_end: BytePos,
next_span_start: BytePos,
+ leave_last: bool,
) -> ListItems<'a, I, F1, F2, F3>
where
I: Iterator<Item = T>,
prev_span_end: prev_span_end,
next_span_start: next_span_start,
terminator: terminator,
- }
-}
-
-fn needs_trailing_separator(
- separator_tactic: SeparatorTactic,
- list_tactic: DefinitiveListTactic,
-) -> bool {
- match separator_tactic {
- SeparatorTactic::Always => true,
- SeparatorTactic::Vertical => list_tactic == DefinitiveListTactic::Vertical,
- SeparatorTactic::Never => false,
+ leave_last: leave_last,
}
}
.fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l))
}
-fn total_item_width(item: &ListItem) -> usize {
- comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..])) +
- comment_len(item.post_comment.as_ref().map(|x| &(*x)[..])) +
- item.item.as_ref().map_or(0, |str| str.len())
+pub fn total_item_width(item: &ListItem) -> usize {
+ comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..]))
+ + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..]))
+ + item.item.as_ref().map_or(0, |str| str.len())
}
fn comment_len(comment: Option<&str>) -> usize {
prefix_width: usize,
suffix_width: usize,
) -> Option<(Option<Shape>, Shape)> {
- let v_shape = match context.config.struct_lit_style() {
- IndentStyle::Visual => try_opt!(
- try_opt!(shape.visual_indent(0).shrink_left(prefix_width)).sub_width(suffix_width)
- ),
+ let v_shape = match context.config.struct_lit_indent() {
+ IndentStyle::Visual => shape
+ .visual_indent(0)
+ .shrink_left(prefix_width)?
+ .sub_width(suffix_width)?,
IndentStyle::Block => {
let shape = shape.block_indent(context.config.tab_spaces());
Shape {
- width: try_opt!(context.config.max_width().checked_sub(shape.indent.width())),
+ width: context.budget(shape.indent.width()),
..shape
}
}
items: &[ListItem],
) -> DefinitiveListTactic {
if let Some(h_shape) = h_shape {
- let prelim_tactic = match (context.config.struct_lit_style(), items.len()) {
+ let prelim_tactic = match (context.config.struct_lit_indent(), items.len()) {
(IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
_ => context.config.struct_lit_multiline_style().to_list_tactic(),
};
context: &'a RewriteContext,
force_no_trailing_comma: bool,
) -> ListFormatting<'a> {
- let ends_with_newline = context.config.struct_lit_style() != IndentStyle::Visual &&
- tactic == DefinitiveListTactic::Vertical;
+ let ends_with_newline = context.config.struct_lit_indent() != IndentStyle::Visual
+ && tactic == DefinitiveListTactic::Vertical;
ListFormatting {
tactic: tactic,
separator: ",",
} else {
context.config.trailing_comma()
},
+ separator_place: SeparatorPlace::Back,
shape: shape,
ends_with_newline: ends_with_newline,
preserve_newline: true,