]> git.lizzy.rs Git - rust.git/blobdiff - src/lists.rs
Remove unnecessary references
[rust.git] / src / lists.rs
index d176ef01f9b57a033dbed7f348a85343a186c40e..42edc3d3d3c66cc62106739927827771e635647b 100644 (file)
 use std::cmp;
 use std::iter::Peekable;
 
-use syntax::codemap::{CodeMap, BytePos};
+use syntax::codemap::{BytePos, CodeMap};
 
 use {Indent, Shape};
-use comment::{FindUncommented, rewrite_comment, find_comment_end};
+use comment::{find_comment_end, rewrite_comment, FindUncommented};
 use config::{Config, IndentStyle};
 use rewrite::RewriteContext;
-use utils::mk_sp;
+use utils::{first_line_width, last_line_width, mk_sp};
 
-#[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,
@@ -61,48 +61,27 @@ pub struct ListFormatting<'a> {
     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 ends_with_newline: bool,
+    // Remove newlines between list elements for expressions.
+    pub preserve_newline: bool,
     pub config: &'a Config,
 }
 
-pub fn format_fn_args<I>(items: I, shape: Shape, config: &Config) -> Option<String>
-where
-    I: Iterator<Item = ListItem>,
-{
-    list_helper(
-        items,
-        shape,
-        config,
-        ListTactic::LimitedHorizontalVertical(config.fn_call_width()),
-    )
-}
-
-pub fn format_item_list<I>(items: I, shape: Shape, config: &Config) -> Option<String>
-where
-    I: Iterator<Item = ListItem>,
-{
-    list_helper(items, shape, config, ListTactic::HorizontalVertical)
-}
-
-pub fn list_helper<I>(items: I, shape: Shape, config: &Config, tactic: ListTactic) -> Option<String>
-where
-    I: Iterator<Item = ListItem>,
-{
-    let item_vec: Vec<_> = items.collect();
-    let tactic = definitive_tactic(&item_vec, tactic, shape.width);
-    let fmt = ListFormatting {
-        tactic: tactic,
-        separator: ",",
-        trailing_separator: SeparatorTactic::Never,
-        shape: shape,
-        ends_with_newline: false,
-        config: config,
-    };
-
-    write_list(&item_vec, &fmt)
+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 {
@@ -111,9 +90,20 @@ fn as_ref(&self) -> &ListItem {
     }
 }
 
+#[derive(PartialEq, Eq)]
+pub enum ListItemCommentStyle {
+    // Try to keep the comment on the same line with the item.
+    SameLine,
+    // Put the comment on the previous or the next line of the item.
+    DifferentLine,
+    // No comment available.
+    None,
+}
+
 pub struct ListItem {
     // None for comments mean that they are not present.
     pub pre_comment: Option<String>,
+    pub pre_comment_style: ListItemCommentStyle,
     // Item should include attributes and doc comments. None indicates a failed
     // rewrite.
     pub item: Option<String>,
@@ -123,22 +113,40 @@ pub struct ListItem {
 }
 
 impl ListItem {
+    pub fn inner_as_ref(&self) -> &str {
+        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.item.as_ref().map_or(false, |s| s.contains('\n')) || self.pre_comment.is_some() ||
+        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_line_pre_comment(&self) -> bool {
+    pub fn has_comment(&self) -> bool {
         self.pre_comment
             .as_ref()
-            .map_or(false, |comment| comment.starts_with("//"))
+            .map_or(false, |comment| comment.starts_with("//")) ||
+            self.post_comment
+                .as_ref()
+                .map_or(false, |comment| comment.starts_with("//"))
     }
 
     pub fn from_str<S: Into<String>>(s: S) -> ListItem {
         ListItem {
             pre_comment: None,
+            pre_comment_style: ListItemCommentStyle::None,
             item: Some(s.into()),
             post_comment: None,
             new_lines: false,
@@ -146,15 +154,73 @@ pub fn from_str<S: Into<String>>(s: S) -> ListItem {
     }
 }
 
-#[derive(Eq, PartialEq, Debug, Copy, Clone)]
 /// The definitive formatting tactic for lists.
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
 pub enum DefinitiveListTactic {
     Vertical,
     Horizontal,
     Mixed,
 }
 
-pub fn definitive_tactic<I, T>(items: I, tactic: ListTactic, width: usize) -> DefinitiveListTactic
+impl DefinitiveListTactic {
+    pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool {
+        match indent_style {
+            IndentStyle::Block => *self != DefinitiveListTactic::Horizontal,
+            IndentStyle::Visual => false,
+        }
+    }
+}
+
+/// The type of separator for lists.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Separator {
+    Comma,
+    VerticalBar,
+}
+
+impl Separator {
+    pub fn len(&self) -> usize {
+        match *self {
+            // 2 = `, `
+            Separator::Comma => 2,
+            // 3 = ` | `
+            Separator::VerticalBar => 3,
+        }
+    }
+}
+
+/// 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,
+    sep: Separator,
+    width: usize,
+) -> DefinitiveListTactic
 where
     I: IntoIterator<Item = T> + Clone,
     T: AsRef<ListItem>,
@@ -162,7 +228,7 @@ pub fn definitive_tactic<I, T>(items: I, tactic: ListTactic, width: usize) -> De
     let pre_line_comments = items
         .clone()
         .into_iter()
-        .any(|item| item.as_ref().has_line_pre_comment());
+        .any(|item| item.as_ref().has_comment());
 
     let limit = match tactic {
         _ if pre_line_comments => return DefinitiveListTactic::Vertical,
@@ -174,8 +240,7 @@ pub fn definitive_tactic<I, T>(items: I, tactic: ListTactic, width: usize) -> De
     };
 
     let (sep_count, total_width) = calculate_width(items.clone());
-    let sep_len = ", ".len(); // FIXME: make more generic?
-    let total_sep_len = sep_len * sep_count.checked_sub(1).unwrap_or(0);
+    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 &&
@@ -191,7 +256,7 @@ pub fn definitive_tactic<I, T>(items: I, tactic: ListTactic, width: usize) -> De
 // TODO: add unit tests
 pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
 where
-    I: IntoIterator<Item = T>,
+    I: IntoIterator<Item = T> + Clone,
     T: AsRef<ListItem>,
 {
     let tactic = formatting.tactic;
@@ -199,9 +264,12 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
 
     // Now that we know how we will layout, we can decide for sure if there
     // will be a trailing separator.
-    let 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);
@@ -210,7 +278,7 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
         let inner_item = try_opt!(item.item.as_ref());
         let first = i == 0;
         let last = iter.peek().is_none();
-        let separate = !last || trailing_separator;
+        let mut separate = !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)
@@ -241,6 +309,16 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
                     result.push('\n');
                     result.push_str(indent_str);
                     line_len = 0;
+                    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 {
@@ -267,13 +345,33 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             result.push_str(&comment);
 
             if tactic == DefinitiveListTactic::Vertical {
-                result.push('\n');
-                result.push_str(indent_str);
+                // We cannot keep pre-comments on the same line if the comment if normalized.
+                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.
+                    // 1 = ` `
+                    let total_width = total_item_width(item) + item_sep_len + 1;
+                    total_width <= formatting.shape.width
+                };
+                if keep_comment {
+                    result.push(' ');
+                } else {
+                    result.push('\n');
+                    result.push_str(indent_str);
+                }
             } else {
                 result.push(' ');
             }
+            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
@@ -290,41 +388,74 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
             result.push_str(&formatted_comment);
         }
 
-        if separate {
+        if separate && sep_place.is_back() {
             result.push_str(formatting.separator);
         }
 
         if tactic == DefinitiveListTactic::Vertical && item.post_comment.is_some() {
-            // 1 = space between item and comment.
-            let width = formatting
-                .shape
-                .width
-                .checked_sub(item_last_line_width + 1)
-                .unwrap_or(1);
-            let mut offset = formatting.shape.indent;
-            offset.alignment += item_last_line_width + 1;
             let comment = item.post_comment.as_ref().unwrap();
+            let overhead = last_line_width(&result) + first_line_width(comment.trim());
+
+            let rewrite_post_comment = |item_max_width: &mut Option<usize>| {
+                if item_max_width.is_none() && !last && !inner_item.contains('\n') {
+                    *item_max_width = Some(max_width_of_item_with_post_comment(
+                        &cloned_items,
+                        i,
+                        overhead,
+                        formatting.config.max_width(),
+                    ));
+                }
+                let overhead = if let Some(max_width) = *item_max_width {
+                    max_width + 2
+                } else {
+                    // 1 = space between item and comment.
+                    item_last_line_width + 1
+                };
+                let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1);
+                let offset = formatting.shape.indent + overhead;
+                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)
+            };
 
-            debug!("Width = {}, offset = {:?}", 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 formatted_comment = try_opt!(rewrite_comment(
-                comment,
-                block_style,
-                Shape::legacy(width, offset),
-                formatting.config,
-            ));
+            let mut formatted_comment = try_opt!(rewrite_post_comment(&mut item_max_width));
 
             if !formatted_comment.starts_with('\n') {
-                result.push(' ');
+                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()
+                {
+                    item_max_width = None;
+                    formatted_comment = try_opt!(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 separator.
+                if last && item_max_width.is_some() && !separate && !formatting.separator.is_empty()
+                {
+                    result.push(' ');
+                }
+            }
+            if formatted_comment.contains('\n') {
+                item_max_width = None;
             }
             result.push_str(&formatted_comment);
+        } else {
+            item_max_width = None;
         }
 
-        if !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');
         }
     }
@@ -332,6 +463,44 @@ pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
     Some(result)
 }
 
+fn max_width_of_item_with_post_comment<I, T>(
+    items: &I,
+    i: usize,
+    overhead: usize,
+    max_budget: usize,
+) -> usize
+where
+    I: IntoIterator<Item = T> + Clone,
+    T: AsRef<ListItem>,
+{
+    let mut max_width = 0;
+    let mut first = true;
+    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)
+        {
+            return max_width;
+        }
+        if max_width < inner_item_width {
+            max_width = inner_item_width;
+        }
+        if item.new_lines {
+            return max_width;
+        }
+        first = false;
+    }
+    max_width
+}
+
+fn post_comment_alignment(item_max_width: Option<usize>, inner_item_len: usize) -> usize {
+    item_max_width
+        .and_then(|max_line_width| max_line_width.checked_sub(inner_item_len))
+        .unwrap_or(0)
+}
+
 pub struct ListItems<'a, I, F1, F2, F3>
 where
     I: Iterator,
@@ -344,6 +513,7 @@ pub struct ListItems<'a, I, F1, F2, F3>
     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>
@@ -365,12 +535,33 @@ fn next(&mut self) -> Option<Self::Item> {
                 .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
                 .unwrap();
             let trimmed_pre_snippet = pre_snippet.trim();
-            let has_pre_comment = trimmed_pre_snippet.contains("//") ||
-                trimmed_pre_snippet.contains("/*");
-            let pre_comment = if has_pre_comment {
-                Some(trimmed_pre_snippet.to_owned())
+            let has_single_line_comment = trimmed_pre_snippet.starts_with("//");
+            let has_block_comment = trimmed_pre_snippet.starts_with("/*");
+            let (pre_comment, pre_comment_style) = if has_single_line_comment {
+                (
+                    Some(trimmed_pre_snippet.to_owned()),
+                    ListItemCommentStyle::DifferentLine,
+                )
+            } else if has_block_comment {
+                let comment_end = pre_snippet.chars().rev().position(|c| c == '/').unwrap();
+                if pre_snippet
+                    .chars()
+                    .rev()
+                    .take(comment_end + 1)
+                    .any(|c| c == '\n')
+                {
+                    (
+                        Some(trimmed_pre_snippet.to_owned()),
+                        ListItemCommentStyle::DifferentLine,
+                    )
+                } else {
+                    (
+                        Some(trimmed_pre_snippet.to_owned()),
+                        ListItemCommentStyle::SameLine,
+                    )
+                }
             } else {
-                None
+                (None, ListItemCommentStyle::None)
             };
 
             // Post-comment
@@ -392,36 +583,35 @@ fn next(&mut self) -> Option<Self::Item> {
                         }
                     }
                     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(
+                    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(
+                            ),
+                            // 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(),
                         }
-                        // 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())
-                }
+                None => post_snippet
+                    .find_uncommented(self.terminator)
+                    .unwrap_or_else(|| post_snippet.len()),
             };
 
             if !post_snippet.is_empty() && comment_end > 0 {
@@ -430,12 +620,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];
 
@@ -449,7 +641,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)
@@ -465,7 +657,12 @@ fn next(&mut self) -> Option<Self::Item> {
 
             ListItem {
                 pre_comment: pre_comment,
-                item: (self.get_item_string)(&item),
+                pre_comment_style: pre_comment_style,
+                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,
             }
@@ -483,6 +680,7 @@ pub fn itemize_list<'a, T, I, F1, F2, F3>(
     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>,
@@ -499,17 +697,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,
-    }
-}
-
-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,
     }
 }
 
@@ -554,21 +742,24 @@ pub fn struct_lit_shape(
     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)
-            )
-        }
+        IndentStyle::Visual => try_opt!(
+            try_opt!(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 h_shape = shape.sub_width(prefix_width + suffix_width);
-    Some((h_shape, v_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());
+        Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
+    } else {
+        Some((None, v_shape))
+    }
 }
 
 // Compute the tactic for the internals of a struct-lit-like thing.
@@ -578,17 +769,11 @@ pub fn struct_lit_tactic(
     items: &[ListItem],
 ) -> DefinitiveListTactic {
     if let Some(h_shape) = h_shape {
-        let mut prelim_tactic = match (context.config.struct_lit_style(), items.len()) {
+        let prelim_tactic = match (context.config.struct_lit_style(), items.len()) {
             (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
             _ => context.config.struct_lit_multiline_style().to_list_tactic(),
         };
-
-        if prelim_tactic == ListTactic::HorizontalVertical && items.len() > 1 {
-            prelim_tactic =
-                ListTactic::LimitedHorizontalVertical(context.config.struct_lit_width());
-        }
-
-        definitive_tactic(items, prelim_tactic, h_shape.width)
+        definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
     } else {
         DefinitiveListTactic::Vertical
     }
@@ -625,8 +810,10 @@ pub fn struct_lit_formatting<'a>(
         } else {
             context.config.trailing_comma()
         },
+        separator_place: SeparatorPlace::Back,
         shape: shape,
         ends_with_newline: ends_with_newline,
+        preserve_newline: true,
         config: context.config,
     }
 }