1 //! Format list-like expressions and items.
4 use std
::iter
::Peekable
;
6 use rustc_span
::BytePos
;
8 use crate::comment
::{find_comment_end, rewrite_comment, FindUncommented}
;
9 use crate::config
::lists
::*;
10 use crate::config
::{Config, IndentStyle}
;
11 use crate::rewrite
::RewriteContext
;
12 use crate::shape
::{Indent, Shape}
;
14 count_newlines
, first_line_width
, last_line_width
, mk_sp
, starts_with_newline
,
17 use crate::visitor
::SnippetProvider
;
19 pub(crate) struct ListFormatting
<'a
> {
20 tactic
: DefinitiveListTactic
,
22 trailing_separator
: SeparatorTactic
,
23 separator_place
: SeparatorPlace
,
25 // Non-expressions, e.g., items, will have a new line at the end of the list.
26 // Important for comment styles.
27 ends_with_newline
: bool
,
28 // Remove newlines between list elements for expressions.
29 preserve_newline
: bool
,
30 // Nested import lists get some special handling for the "Mixed" list type
32 // Whether comments should be visually aligned.
37 impl<'a
> ListFormatting
<'a
> {
38 pub(crate) fn new(shape
: Shape
, config
: &'a Config
) -> Self {
40 tactic
: DefinitiveListTactic
::Vertical
,
42 trailing_separator
: SeparatorTactic
::Never
,
43 separator_place
: SeparatorPlace
::Back
,
45 ends_with_newline
: true,
46 preserve_newline
: false,
53 pub(crate) fn tactic(mut self, tactic
: DefinitiveListTactic
) -> Self {
58 pub(crate) fn separator(mut self, separator
: &'a
str) -> Self {
59 self.separator
= separator
;
63 pub(crate) fn trailing_separator(mut self, trailing_separator
: SeparatorTactic
) -> Self {
64 self.trailing_separator
= trailing_separator
;
68 pub(crate) fn separator_place(mut self, separator_place
: SeparatorPlace
) -> Self {
69 self.separator_place
= separator_place
;
73 pub(crate) fn ends_with_newline(mut self, ends_with_newline
: bool
) -> Self {
74 self.ends_with_newline
= ends_with_newline
;
78 pub(crate) fn preserve_newline(mut self, preserve_newline
: bool
) -> Self {
79 self.preserve_newline
= preserve_newline
;
83 pub(crate) fn nested(mut self, nested
: bool
) -> Self {
88 pub(crate) fn align_comments(mut self, align_comments
: bool
) -> Self {
89 self.align_comments
= align_comments
;
93 pub(crate) fn needs_trailing_separator(&self) -> bool
{
94 match self.trailing_separator
{
95 // We always put separator in front.
96 SeparatorTactic
::Always
=> true,
97 SeparatorTactic
::Vertical
=> self.tactic
== DefinitiveListTactic
::Vertical
,
98 SeparatorTactic
::Never
=> {
99 self.tactic
== DefinitiveListTactic
::Vertical
&& self.separator_place
.is_front()
105 impl AsRef
<ListItem
> for ListItem
{
106 fn as_ref(&self) -> &ListItem
{
111 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
112 pub(crate) enum ListItemCommentStyle
{
113 // Try to keep the comment on the same line with the item.
115 // Put the comment on the previous or the next line of the item.
117 // No comment available.
121 #[derive(Debug, Clone)]
122 pub(crate) struct ListItem
{
123 // None for comments mean that they are not present.
124 pub(crate) pre_comment
: Option
<String
>,
125 pub(crate) pre_comment_style
: ListItemCommentStyle
,
126 // Item should include attributes and doc comments. None indicates a failed
128 pub(crate) item
: Option
<String
>,
129 pub(crate) post_comment
: Option
<String
>,
130 // Whether there is extra whitespace before this item.
131 pub(crate) new_lines
: bool
,
135 pub(crate) fn empty() -> ListItem
{
138 pre_comment_style
: ListItemCommentStyle
::None
,
145 pub(crate) fn inner_as_ref(&self) -> &str {
146 self.item
.as_ref().map_or("", |s
| s
)
149 pub(crate) fn is_different_group(&self) -> bool
{
150 self.inner_as_ref().contains('
\n'
)
151 || self.pre_comment
.is_some()
155 .map_or(false, |s
| s
.contains('
\n'
))
158 pub(crate) fn is_multiline(&self) -> bool
{
159 self.inner_as_ref().contains('
\n'
)
163 .map_or(false, |s
| s
.contains('
\n'
))
167 .map_or(false, |s
| s
.contains('
\n'
))
170 pub(crate) fn has_single_line_comment(&self) -> bool
{
173 .map_or(false, |comment
| comment
.trim_start().starts_with("//"))
177 .map_or(false, |comment
| comment
.trim_start().starts_with("//"))
180 pub(crate) fn has_comment(&self) -> bool
{
181 self.pre_comment
.is_some() || self.post_comment
.is_some()
184 pub(crate) fn from_str
<S
: Into
<String
>>(s
: S
) -> ListItem
{
187 pre_comment_style
: ListItemCommentStyle
::None
,
188 item
: Some(s
.into()),
194 // Returns `true` if the item causes something to be written.
195 fn is_substantial(&self) -> bool
{
196 fn empty(s
: &Option
<String
>) -> bool
{
197 !matches
!(*s
, Some(ref s
) if !s
.is_empty())
200 !(empty(&self.pre_comment
) && empty(&self.item
) && empty(&self.post_comment
))
204 /// The type of separator for lists.
205 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
206 pub(crate) enum Separator
{
212 pub(crate) fn len(self) -> usize {
215 Separator
::Comma
=> 2,
217 Separator
::VerticalBar
=> 3,
222 pub(crate) fn definitive_tactic
<I
, T
>(
227 ) -> DefinitiveListTactic
229 I
: IntoIterator
<Item
= T
> + Clone
,
232 let pre_line_comments
= items
235 .any(|item
| item
.as_ref().has_single_line_comment());
237 let limit
= match tactic
{
238 _
if pre_line_comments
=> return DefinitiveListTactic
::Vertical
,
239 ListTactic
::Horizontal
=> return DefinitiveListTactic
::Horizontal
,
240 ListTactic
::Vertical
=> return DefinitiveListTactic
::Vertical
,
241 ListTactic
::LimitedHorizontalVertical(limit
) => ::std
::cmp
::min(width
, limit
),
242 ListTactic
::Mixed
| ListTactic
::HorizontalVertical
=> width
,
245 let (sep_count
, total_width
) = calculate_width(items
.clone());
246 let total_sep_len
= sep
.len() * sep_count
.saturating_sub(1);
247 let real_total
= total_width
+ total_sep_len
;
249 if real_total
<= limit
&& !items
.into_iter().any(|item
| item
.as_ref().is_multiline()) {
250 DefinitiveListTactic
::Horizontal
253 ListTactic
::Mixed
=> DefinitiveListTactic
::Mixed
,
254 _
=> DefinitiveListTactic
::Vertical
,
259 // Format a list of commented items into a string.
260 pub(crate) fn write_list
<I
, T
>(items
: I
, formatting
: &ListFormatting
<'_
>) -> Option
<String
>
262 I
: IntoIterator
<Item
= T
> + Clone
,
265 let tactic
= formatting
.tactic
;
266 let sep_len
= formatting
.separator
.len();
268 // Now that we know how we will layout, we can decide for sure if there
269 // will be a trailing separator.
270 let mut trailing_separator
= formatting
.needs_trailing_separator();
271 let mut result
= String
::with_capacity(128);
272 let cloned_items
= items
.clone();
273 let mut iter
= items
.into_iter().enumerate().peekable();
274 let mut item_max_width
: Option
<usize> = None
;
276 SeparatorPlace
::from_tactic(formatting
.separator_place
, tactic
, formatting
.separator
);
277 let mut prev_item_had_post_comment
= false;
278 let mut prev_item_is_nested_import
= false;
280 let mut line_len
= 0;
281 let indent_str
= &formatting
.shape
.indent
.to_string(formatting
.config
);
282 while let Some((i
, item
)) = iter
.next() {
283 let item
= item
.as_ref();
284 let inner_item
= item
.item
.as_ref()?
;
286 let last
= iter
.peek().is_none();
287 let mut separate
= match sep_place
{
288 SeparatorPlace
::Front
=> !first
,
289 SeparatorPlace
::Back
=> !last
|| trailing_separator
,
291 let item_sep_len
= if separate { sep_len }
else { 0 }
;
293 // Item string may be multi-line. Its length (used for block comment alignment)
294 // should be only the length of the last line.
295 let item_last_line
= if item
.is_multiline() {
296 inner_item
.lines().last().unwrap_or("")
300 let mut item_last_line_width
= item_last_line
.len() + item_sep_len
;
301 if item_last_line
.starts_with(&**indent_str
) {
302 item_last_line_width
-= indent_str
.len();
305 if !item
.is_substantial() {
310 DefinitiveListTactic
::Horizontal
if !first
=> {
313 DefinitiveListTactic
::SpecialMacro(num_args_before
) => {
316 } else if i
< num_args_before
{
318 } else if i
<= num_args_before
+ 1 {
320 result
.push_str(indent_str
);
325 DefinitiveListTactic
::Vertical
326 if !first
&& !inner_item
.is_empty() && !result
.is_empty() =>
329 result
.push_str(indent_str
);
331 DefinitiveListTactic
::Mixed
=> {
332 let total_width
= total_item_width(item
) + item_sep_len
;
334 // 1 is space between separator and item.
335 if (line_len
> 0 && line_len
+ 1 + total_width
> formatting
.shape
.width
)
336 || prev_item_had_post_comment
337 || (formatting
.nested
338 && (prev_item_is_nested_import
|| (!first
&& inner_item
.contains("::"))))
341 result
.push_str(indent_str
);
343 if formatting
.ends_with_newline
{
344 trailing_separator
= true;
346 } else if line_len
> 0 {
351 if last
&& formatting
.ends_with_newline
{
352 separate
= formatting
.trailing_separator
!= SeparatorTactic
::Never
;
355 line_len
+= total_width
;
361 if let Some(ref comment
) = item
.pre_comment
{
362 // Block style in non-vertical mode.
363 let block_mode
= tactic
== DefinitiveListTactic
::Horizontal
;
364 // Width restriction is only relevant in vertical mode.
366 rewrite_comment(comment
, block_mode
, formatting
.shape
, formatting
.config
)?
;
367 result
.push_str(&comment
);
369 if !inner_item
.is_empty() {
370 if tactic
== DefinitiveListTactic
::Vertical
|| tactic
== DefinitiveListTactic
::Mixed
372 // We cannot keep pre-comments on the same line if the comment if normalized.
373 let keep_comment
= if formatting
.config
.normalize_comments()
374 || item
.pre_comment_style
== ListItemCommentStyle
::DifferentLine
378 // We will try to keep the comment on the same line with the item here.
380 let total_width
= total_item_width(item
) + item_sep_len
+ 1;
381 total_width
<= formatting
.shape
.width
387 result
.push_str(indent_str
);
388 // This is the width of the item (without comments).
389 line_len
= item
.item
.as_ref().map_or(0, |s
| unicode_str_width(&s
));
395 item_max_width
= None
;
398 if separate
&& sep_place
.is_front() && !first
{
399 result
.push_str(formatting
.separator
.trim());
402 result
.push_str(inner_item
);
405 if tactic
== DefinitiveListTactic
::Horizontal
&& item
.post_comment
.is_some() {
406 let comment
= item
.post_comment
.as_ref().unwrap();
407 let formatted_comment
= rewrite_comment(
410 Shape
::legacy(formatting
.shape
.width
, Indent
::empty()),
415 result
.push_str(&formatted_comment
);
418 if separate
&& sep_place
.is_back() {
419 result
.push_str(formatting
.separator
);
422 if tactic
!= DefinitiveListTactic
::Horizontal
&& item
.post_comment
.is_some() {
423 let comment
= item
.post_comment
.as_ref().unwrap();
424 let overhead
= last_line_width(&result
) + first_line_width(comment
.trim());
426 let rewrite_post_comment
= |item_max_width
: &mut Option
<usize>| {
427 if item_max_width
.is_none() && !last
&& !inner_item
.contains('
\n'
) {
428 *item_max_width
= Some(max_width_of_item_with_post_comment(
432 formatting
.config
.max_width(),
435 let overhead
= if starts_with_newline(comment
) {
437 } else if let Some(max_width
) = *item_max_width
{
440 // 1 = space between item and comment.
441 item_last_line_width
+ 1
443 let width
= formatting
.shape
.width
.checked_sub(overhead
).unwrap_or(1);
444 let offset
= formatting
.shape
.indent
+ overhead
;
445 let comment_shape
= Shape
::legacy(width
, offset
);
447 // Use block-style only for the last item or multiline comments.
448 let block_style
= !formatting
.ends_with_newline
&& last
449 || comment
.trim().contains('
\n'
)
450 || comment
.trim().len() > width
;
453 comment
.trim_start(),
460 let mut formatted_comment
= rewrite_post_comment(&mut item_max_width
)?
;
462 if !starts_with_newline(comment
) {
463 if formatting
.align_comments
{
464 let mut comment_alignment
=
465 post_comment_alignment(item_max_width
, inner_item
.len());
466 if first_line_width(&formatted_comment
)
467 + last_line_width(&result
)
470 > formatting
.config
.max_width()
472 item_max_width
= None
;
473 formatted_comment
= rewrite_post_comment(&mut item_max_width
)?
;
475 post_comment_alignment(item_max_width
, inner_item
.len());
477 for _
in 0..=comment_alignment
{
481 // An additional space for the missing trailing separator (or
482 // if we skipped alignment above).
483 if !formatting
.align_comments
485 && item_max_width
.is_some()
487 && !formatting
.separator
.is_empty())
493 result
.push_str(indent_str
);
495 if formatted_comment
.contains('
\n'
) {
496 item_max_width
= None
;
498 result
.push_str(&formatted_comment
);
500 item_max_width
= None
;
503 if formatting
.preserve_newline
505 && tactic
== DefinitiveListTactic
::Vertical
508 item_max_width
= None
;
512 prev_item_had_post_comment
= item
.post_comment
.is_some();
513 prev_item_is_nested_import
= inner_item
.contains("::");
519 fn max_width_of_item_with_post_comment
<I
, T
>(
526 I
: IntoIterator
<Item
= T
> + Clone
,
529 let mut max_width
= 0;
530 let mut first
= true;
531 for item
in items
.clone().into_iter().skip(i
) {
532 let item
= item
.as_ref();
533 let inner_item_width
= item
.inner_as_ref().len();
535 && (item
.is_different_group()
536 || item
.post_comment
.is_none()
537 || inner_item_width
+ overhead
> max_budget
)
541 if max_width
< inner_item_width
{
542 max_width
= inner_item_width
;
552 fn post_comment_alignment(item_max_width
: Option
<usize>, inner_item_len
: usize) -> usize {
553 item_max_width
.unwrap_or(0).saturating_sub(inner_item_len
)
556 pub(crate) struct ListItems
<'a
, I
, F1
, F2
, F3
>
560 snippet_provider
: &'a SnippetProvider
,
565 prev_span_end
: BytePos
,
566 next_span_start
: BytePos
,
572 pub(crate) fn extract_pre_comment(pre_snippet
: &str) -> (Option
<String
>, ListItemCommentStyle
) {
573 let trimmed_pre_snippet
= pre_snippet
.trim();
574 // Both start and end are checked to support keeping a block comment inline with
575 // the item, even if there are preceeding line comments, while still supporting
576 // a snippet that starts with a block comment but also contains one or more
577 // trailing single line comments.
578 // https://github.com/rust-lang/rustfmt/issues/3025
579 // https://github.com/rust-lang/rustfmt/pull/3048
580 // https://github.com/rust-lang/rustfmt/issues/3839
581 let starts_with_block_comment
= trimmed_pre_snippet
.starts_with("/*");
582 let ends_with_block_comment
= trimmed_pre_snippet
.ends_with("*/");
583 let starts_with_single_line_comment
= trimmed_pre_snippet
.starts_with("//");
584 if ends_with_block_comment
{
585 let comment_end
= pre_snippet
.rfind(|c
| c
== '
/'
).unwrap();
586 if pre_snippet
[comment_end
..].contains('
\n'
) {
588 Some(trimmed_pre_snippet
.to_owned()),
589 ListItemCommentStyle
::DifferentLine
,
593 Some(trimmed_pre_snippet
.to_owned()),
594 ListItemCommentStyle
::SameLine
,
597 } else if starts_with_single_line_comment
|| starts_with_block_comment
{
599 Some(trimmed_pre_snippet
.to_owned()),
600 ListItemCommentStyle
::DifferentLine
,
603 (None
, ListItemCommentStyle
::None
)
607 pub(crate) fn extract_post_comment(
611 ) -> Option
<String
> {
612 let white_space
: &[_
] = &[' '
, '
\t'
];
614 // Cleanup post-comment: strip separators and whitespace.
615 let post_snippet
= post_snippet
[..comment_end
].trim();
616 let post_snippet_trimmed
= if post_snippet
.starts_with(|c
| c
== '
,'
|| c
== '
:'
) {
617 post_snippet
[1..].trim_matches(white_space
)
618 } else if let Some(stripped
) = post_snippet
.strip_prefix(separator
) {
619 stripped
.trim_matches(white_space
)
621 // not comment or over two lines
622 else if post_snippet
.ends_with('
,'
)
623 && (!post_snippet
.trim().starts_with("//") || post_snippet
.trim().contains('
\n'
))
625 post_snippet
[..(post_snippet
.len() - 1)].trim_matches(white_space
)
629 // FIXME(#3441): post_snippet includes 'const' now
630 // it should not include here
631 let removed_newline_snippet
= post_snippet_trimmed
.trim();
632 if !post_snippet_trimmed
.is_empty()
633 && (removed_newline_snippet
.starts_with("//") || removed_newline_snippet
.starts_with("/*"))
635 Some(post_snippet_trimmed
.to_owned())
641 pub(crate) fn get_comment_end(
649 .find_uncommented(terminator
)
650 .unwrap_or_else(|| post_snippet
.len());
653 let mut block_open_index
= post_snippet
.find("/*");
654 // check if it really is a block comment (and not `//*` or a nested comment)
655 if let Some(i
) = block_open_index
{
656 match post_snippet
.find('
/'
) {
657 Some(j
) if j
< i
=> block_open_index
= None
,
658 _
if post_snippet
[..i
].ends_with('
/'
) => block_open_index
= None
,
662 let newline_index
= post_snippet
.find('
\n'
);
663 if let Some(separator_index
) = post_snippet
.find_uncommented(separator
) {
664 match (block_open_index
, newline_index
) {
665 // Separator before comment, with the next item on same line.
666 // Comment belongs to next item.
667 (Some(i
), None
) if i
> separator_index
=> separator_index
+ 1,
668 // Block-style post-comment before the separator.
669 (Some(i
), None
) => cmp
::max(
670 find_comment_end(&post_snippet
[i
..]).unwrap() + i
,
673 // Block-style post-comment. Either before or after the separator.
674 (Some(i
), Some(j
)) if i
< j
=> cmp
::max(
675 find_comment_end(&post_snippet
[i
..]).unwrap() + i
,
678 // Potential *single* line comment.
679 (_
, Some(j
)) if j
> separator_index
=> j
+ 1,
680 _
=> post_snippet
.len(),
682 } else if let Some(newline_index
) = newline_index
{
683 // Match arms may not have trailing comma. In any case, for match arms,
684 // we will assume that the post comment belongs to the next arm if they
685 // do not end with trailing comma.
692 // Account for extra whitespace between items. This is fiddly
693 // because of the way we divide pre- and post- comments.
694 pub(crate) fn has_extra_newline(post_snippet
: &str, comment_end
: usize) -> bool
{
695 if post_snippet
.is_empty() || comment_end
== 0 {
699 let len_last
= post_snippet
[..comment_end
]
704 // Everything from the separator to the next item.
705 let test_snippet
= &post_snippet
[comment_end
- len_last
..];
706 let first_newline
= test_snippet
708 .unwrap_or_else(|| test_snippet
.len());
709 // From the end of the first line of comments.
710 let test_snippet
= &test_snippet
[first_newline
..];
711 let first
= test_snippet
712 .find(|c
: char| !c
.is_whitespace())
713 .unwrap_or_else(|| test_snippet
.len());
714 // From the end of the first line of comments to the next non-whitespace char.
715 let test_snippet
= &test_snippet
[..first
];
717 // There were multiple line breaks which got trimmed to nothing.
718 count_newlines(test_snippet
) > 1
721 impl<'a
, T
, I
, F1
, F2
, F3
> Iterator
for ListItems
<'a
, I
, F1
, F2
, F3
>
723 I
: Iterator
<Item
= T
>,
724 F1
: Fn(&T
) -> BytePos
,
725 F2
: Fn(&T
) -> BytePos
,
726 F3
: Fn(&T
) -> Option
<String
>,
728 type Item
= ListItem
;
730 fn next(&mut self) -> Option
<Self::Item
> {
731 self.inner
.next().map(|item
| {
733 let pre_snippet
= self
735 .span_to_snippet(mk_sp(self.prev_span_end
, (self.get_lo
)(&item
)))
737 let (pre_comment
, pre_comment_style
) = extract_pre_comment(pre_snippet
);
740 let next_start
= match self.inner
.peek() {
741 Some(next_item
) => (self.get_lo
)(next_item
),
742 None
=> self.next_span_start
,
744 let post_snippet
= self
746 .span_to_snippet(mk_sp((self.get_hi
)(&item
), next_start
))
748 let comment_end
= get_comment_end(
752 self.inner
.peek().is_none(),
754 let new_lines
= has_extra_newline(post_snippet
, comment_end
);
755 let post_comment
= extract_post_comment(post_snippet
, comment_end
, self.separator
);
757 self.prev_span_end
= (self.get_hi
)(&item
) + BytePos(comment_end
as u32);
762 item
: if self.inner
.peek().is_none() && self.leave_last
{
765 (self.get_item_string
)(&item
)
774 #[allow(clippy::too_many_arguments)]
775 // Creates an iterator over a list's items with associated comments.
776 pub(crate) fn itemize_list
<'a
, T
, I
, F1
, F2
, F3
>(
777 snippet_provider
: &'a SnippetProvider
,
784 prev_span_end
: BytePos
,
785 next_span_start
: BytePos
,
787 ) -> ListItems
<'a
, I
, F1
, F2
, F3
>
789 I
: Iterator
<Item
= T
>,
790 F1
: Fn(&T
) -> BytePos
,
791 F2
: Fn(&T
) -> BytePos
,
792 F3
: Fn(&T
) -> Option
<String
>,
796 inner
: inner
.peekable(),
808 /// Returns the count and total width of the list items.
809 fn calculate_width
<I
, T
>(items
: I
) -> (usize, usize)
811 I
: IntoIterator
<Item
= T
>,
816 .map(|item
| total_item_width(item
.as_ref()))
817 .fold((0, 0), |acc
, l
| (acc
.0 + 1, acc
.1 + l
))
820 pub(crate) fn total_item_width(item
: &ListItem
) -> usize {
821 comment_len(item
.pre_comment
.as_ref().map(|x
| &(*x
)[..]))
822 + comment_len(item
.post_comment
.as_ref().map(|x
| &(*x
)[..]))
823 + item
.item
.as_ref().map_or(0, |s
| unicode_str_width(&s
))
826 fn comment_len(comment
: Option
<&str>) -> usize {
829 let text_len
= s
.trim().len();
831 // We'll put " /*" before and " */" after inline comments.
841 // Compute horizontal and vertical shapes for a struct-lit-like thing.
842 pub(crate) fn struct_lit_shape(
844 context
: &RewriteContext
<'_
>,
847 ) -> Option
<(Option
<Shape
>, Shape
)> {
848 let v_shape
= match context
.config
.indent_style() {
849 IndentStyle
::Visual
=> shape
851 .shrink_left(prefix_width
)?
852 .sub_width(suffix_width
)?
,
853 IndentStyle
::Block
=> {
854 let shape
= shape
.block_indent(context
.config
.tab_spaces());
856 width
: context
.budget(shape
.indent
.width()),
861 let shape_width
= shape
.width
.checked_sub(prefix_width
+ suffix_width
);
862 if let Some(w
) = shape_width
{
863 let shape_width
= cmp
::min(w
, context
.config
.struct_lit_width());
864 Some((Some(Shape
::legacy(shape_width
, shape
.indent
)), v_shape
))
866 Some((None
, v_shape
))
870 // Compute the tactic for the internals of a struct-lit-like thing.
871 pub(crate) fn struct_lit_tactic(
872 h_shape
: Option
<Shape
>,
873 context
: &RewriteContext
<'_
>,
875 ) -> DefinitiveListTactic
{
876 if let Some(h_shape
) = h_shape
{
877 let prelim_tactic
= match (context
.config
.indent_style(), items
.len()) {
878 (IndentStyle
::Visual
, 1) => ListTactic
::HorizontalVertical
,
879 _
if context
.config
.struct_lit_single_line() => ListTactic
::HorizontalVertical
,
880 _
=> ListTactic
::Vertical
,
882 definitive_tactic(items
, prelim_tactic
, Separator
::Comma
, h_shape
.width
)
884 DefinitiveListTactic
::Vertical
888 // Given a tactic and possible shapes for horizontal and vertical layout,
889 // come up with the actual shape to use.
890 pub(crate) fn shape_for_tactic(
891 tactic
: DefinitiveListTactic
,
892 h_shape
: Option
<Shape
>,
896 DefinitiveListTactic
::Horizontal
=> h_shape
.unwrap(),
901 // Create a ListFormatting object for formatting the internals of a
902 // struct-lit-like thing, that is a series of fields.
903 pub(crate) fn struct_lit_formatting
<'a
>(
905 tactic
: DefinitiveListTactic
,
906 context
: &'a RewriteContext
<'_
>,
907 force_no_trailing_comma
: bool
,
908 ) -> ListFormatting
<'a
> {
909 let ends_with_newline
= context
.config
.indent_style() != IndentStyle
::Visual
910 && tactic
== DefinitiveListTactic
::Vertical
;
914 trailing_separator
: if force_no_trailing_comma
{
915 SeparatorTactic
::Never
917 context
.config
.trailing_comma()
919 separator_place
: SeparatorPlace
::Back
,
922 preserve_newline
: true,
924 align_comments
: true,
925 config
: context
.config
,