]> git.lizzy.rs Git - rust.git/blobdiff - src/lists.rs
Handle special-case macros
[rust.git] / src / lists.rs
index a9d705b4c64938e50840cb103296b71f7b98b1df..a51653b3732ae37a7404d5c028d7863a0bc33e30 100644 (file)
 
 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};
 
 /// 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 {
@@ -114,22 +114,22 @@ pub struct ListItem {
 
 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
+        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
+        self.inner_as_ref().contains('\n')
+            || self.pre_comment
                 .as_ref()
-                .map_or(false, |s| s.contains('\n')) ||
-            self.post_comment
+                .map_or(false, |s| s.contains('\n'))
+            || self.post_comment
                 .as_ref()
                 .map_or(false, |s| s.contains('\n'))
     }
@@ -137,10 +137,10 @@ pub fn is_multiline(&self) -> bool {
     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.trim_left().starts_with("//"))
+            || self.post_comment
                 .as_ref()
-                .map_or(false, |comment| comment.starts_with("//"))
+                .map_or(false, |comment| comment.trim_left().starts_with("//"))
     }
 
     pub fn from_str<S: Into<String>>(s: S) -> ListItem {
@@ -160,6 +160,10 @@ pub enum DefinitiveListTactic {
     Vertical,
     Horizontal,
     Mixed,
+    // Special case tactic for `format!()` variants.
+    FormatCall,
+    // Special case tactic for `write!()` varianta.
+    WriteCall,
 }
 
 impl DefinitiveListTactic {
@@ -172,7 +176,7 @@ pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool {
 }
 
 /// The type of separator for lists.
-#[derive(Eq, PartialEq, Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
 pub enum Separator {
     Comma,
     VerticalBar,
@@ -207,10 +211,18 @@ pub fn is_back(&self) -> bool {
         *self == SeparatorPlace::Back
     }
 
-    pub fn from_tactic(default: SeparatorPlace, tactic: DefinitiveListTactic) -> SeparatorPlace {
+    pub fn from_tactic(
+        default: SeparatorPlace,
+        tactic: DefinitiveListTactic,
+        sep: &str,
+    ) -> SeparatorPlace {
         match tactic {
             DefinitiveListTactic::Vertical => default,
-            _ => SeparatorPlace::Back,
+            _ => if sep == "," {
+                SeparatorPlace::Back
+            } else {
+                default
+            },
         }
     }
 }
@@ -243,8 +255,8 @@ pub fn definitive_tactic<I, T>(
     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 {
@@ -259,26 +271,30 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
     I: IntoIterator<Item = T> + Clone,
     T: AsRef<ListItem>,
 {
-    let tactic = formatting.tactic;
+    let mut tactic = formatting.tactic;
     let sep_len = formatting.separator.len();
 
     // Now that we know how we will layout, we can decide for sure if there
     // will be a trailing separator.
     let mut trailing_separator = formatting.needs_trailing_separator();
-    let mut result = String::new();
+    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 sep_place =
+        SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator);
 
     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;
+        let mut separate = match sep_place {
+            SeparatorPlace::Front => !first,
+            SeparatorPlace::Back => !last || trailing_separator,
+        };
         let item_sep_len = if separate { sep_len } else { 0 };
 
         // Item string may be multi-line. Its length (used for block comment alignment)
@@ -289,7 +305,7 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             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();
         }
 
@@ -297,6 +313,28 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             DefinitiveListTactic::Horizontal if !first => {
                 result.push(' ');
             }
+            DefinitiveListTactic::FormatCall if !first => {
+                result.push('\n');
+                result.push_str(indent_str);
+                tactic = DefinitiveListTactic::Horizontal;
+            }
+            DefinitiveListTactic::WriteCall => {
+                let second = i == 1;
+                let third = i == 2;
+
+                if first {
+                    // Nothing
+                } else if second {
+                    result.push('\n');
+                    result.push_str(indent_str);
+                } else if third {
+                    result.push('\n');
+                    result.push_str(indent_str);
+                    tactic = DefinitiveListTactic::Horizontal;
+                } else {
+                    unreachable!();
+                }
+            }
             DefinitiveListTactic::Vertical if !first => {
                 result.push('\n');
                 result.push_str(indent_str);
@@ -316,9 +354,6 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                             trailing_separator = true;
                         }
                     }
-                    sep_place = formatting.separator_place;
-                } else {
-                    sep_place = SeparatorPlace::Back;
                 }
 
                 if line_len > 0 {
@@ -336,19 +371,15 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             // 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.
@@ -377,12 +408,12 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
         // 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);
@@ -405,7 +436,9 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                         formatting.config.max_width(),
                     ));
                 }
-                let overhead = if let &mut Some(max_width) = item_max_width {
+                let overhead = if starts_with_newline(comment) {
+                    0
+                } else if let Some(max_width) = *item_max_width {
                     max_width + 2
                 } else {
                     // 1 = space between item and comment.
@@ -416,23 +449,28 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                 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;
-
-                rewrite_comment(comment, block_style, comment_shape, formatting.config)
+                let block_style = !formatting.ends_with_newline && last
+                    || comment.trim().contains('\n')
+                    || comment.trim().len() > width;
+
+                rewrite_comment(
+                    comment.trim_left(),
+                    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(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) {
@@ -443,6 +481,9 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                 {
                     result.push(' ');
                 }
+            } else {
+                result.push('\n');
+                result.push_str(indent_str);
             }
             if formatted_comment.contains('\n') {
                 item_max_width = None;
@@ -452,8 +493,8 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             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');
@@ -478,9 +519,9 @@ fn max_width_of_item_with_post_comment<I, T>(
     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_different_group() || !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;
         }
@@ -513,6 +554,7 @@ pub struct ListItems<'a, I, F1, F2, F3>
     prev_span_end: BytePos,
     next_span_start: BytePos,
     terminator: &'a str,
+    separator: &'a str,
     leave_last: bool,
 }
 
@@ -548,8 +590,7 @@ fn next(&mut self) -> Option<Self::Item> {
                     .chars()
                     .rev()
                     .take(comment_end + 1)
-                    .find(|c| *c == '\n')
-                    .is_some()
+                    .any(|c| c == '\n')
                 {
                     (
                         Some(trimmed_pre_snippet.to_owned()),
@@ -577,14 +618,16 @@ fn next(&mut self) -> Option<Self::Item> {
             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 `//*` or a nested comment)
                     if let Some(i) = block_open_index {
-                        if i > 0 && &post_snippet[i - 1..i] == "/" {
-                            block_open_index = None;
+                        match post_snippet.find('/') {
+                            Some(j) if j < i => block_open_index = None,
+                            _ if i > 0 && &post_snippet[i - 1..i] == "/" => block_open_index = None,
+                            _ => (),
                         }
                     }
                     let newline_index = post_snippet.find('\n');
-                    if let Some(separator_index) = post_snippet.find_uncommented(",") {
+                    if let Some(separator_index) = post_snippet.find_uncommented(self.separator) {
                         match (block_open_index, newline_index) {
                             // Separator before comment, with the next item on same line.
                             // Comment belongs to next item.
@@ -603,16 +646,18 @@ fn next(&mut self) -> Option<Self::Item> {
                             (_, Some(j)) if j > separator_index => j + 1,
                             _ => post_snippet.len(),
                         }
-                    } else {
+                    } else if let Some(newline_index) = newline_index {
                         // 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
+                        newline_index + 1
+                    } else {
+                        0
                     }
                 }
                 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 {
@@ -621,12 +666,14 @@ fn next(&mut self) -> Option<Self::Item> {
 
                 // 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];
 
@@ -640,7 +687,7 @@ fn next(&mut self) -> Option<Self::Item> {
             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)
@@ -674,6 +721,7 @@ pub fn itemize_list<'a, T, I, F1, F2, F3>(
     codemap: &'a CodeMap,
     inner: I,
     terminator: &'a str,
+    separator: &'a str,
     get_lo: F1,
     get_hi: F2,
     get_item_string: F3,
@@ -696,6 +744,7 @@ pub fn itemize_list<'a, T, I, F1, F2, F3>(
         prev_span_end: prev_span_end,
         next_span_start: next_span_start,
         terminator: terminator,
+        separator: separator,
         leave_last: leave_last,
     }
 }
@@ -712,10 +761,10 @@ fn calculate_width<I, T>(items: I) -> (usize, usize)
         .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 {
@@ -740,21 +789,22 @@ pub fn struct_lit_shape(
     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.indent_style() {
+        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
             }
         }
     };
     let shape_width = shape.width.checked_sub(prefix_width + suffix_width);
     if let Some(w) = shape_width {
-        let shape_width = cmp::min(w, context.config.struct_lit_width());
+        let shape_width = cmp::min(w, context.config.width_heuristics().struct_lit_width);
         Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
     } else {
         Some((None, v_shape))
@@ -768,9 +818,10 @@ pub fn struct_lit_tactic(
     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.indent_style(), items.len()) {
             (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
-            _ => context.config.struct_lit_multiline_style().to_list_tactic(),
+            _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
+            _ => ListTactic::Vertical,
         };
         definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
     } else {
@@ -799,8 +850,8 @@ pub fn struct_lit_formatting<'a>(
     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.indent_style() != IndentStyle::Visual
+        && tactic == DefinitiveListTactic::Vertical;
     ListFormatting {
         tactic: tactic,
         separator: ",",