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 use DefinitiveListTactic
::*;
371 if matches
!(tactic
, Vertical
| Mixed
| SpecialMacro(_
)) {
372 // We cannot keep pre-comments on the same line if the comment is 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 let block_style
= if !formatting
.ends_with_newline
&& last
{
449 } else if starts_with_newline(comment
) {
452 comment
.trim().contains('
\n'
) || comment
.trim().len() > width
456 comment
.trim_start(),
463 let mut formatted_comment
= rewrite_post_comment(&mut item_max_width
)?
;
465 if !starts_with_newline(comment
) {
466 if formatting
.align_comments
{
467 let mut comment_alignment
=
468 post_comment_alignment(item_max_width
, inner_item
.len());
469 if first_line_width(&formatted_comment
)
470 + last_line_width(&result
)
473 > formatting
.config
.max_width()
475 item_max_width
= None
;
476 formatted_comment
= rewrite_post_comment(&mut item_max_width
)?
;
478 post_comment_alignment(item_max_width
, inner_item
.len());
480 for _
in 0..=comment_alignment
{
484 // An additional space for the missing trailing separator (or
485 // if we skipped alignment above).
486 if !formatting
.align_comments
488 && item_max_width
.is_some()
490 && !formatting
.separator
.is_empty())
496 result
.push_str(indent_str
);
498 if formatted_comment
.contains('
\n'
) {
499 item_max_width
= None
;
501 result
.push_str(&formatted_comment
);
503 item_max_width
= None
;
506 if formatting
.preserve_newline
508 && tactic
== DefinitiveListTactic
::Vertical
511 item_max_width
= None
;
515 prev_item_had_post_comment
= item
.post_comment
.is_some();
516 prev_item_is_nested_import
= inner_item
.contains("::");
522 fn max_width_of_item_with_post_comment
<I
, T
>(
529 I
: IntoIterator
<Item
= T
> + Clone
,
532 let mut max_width
= 0;
533 let mut first
= true;
534 for item
in items
.clone().into_iter().skip(i
) {
535 let item
= item
.as_ref();
536 let inner_item_width
= item
.inner_as_ref().len();
538 && (item
.is_different_group()
539 || item
.post_comment
.is_none()
540 || inner_item_width
+ overhead
> max_budget
)
544 if max_width
< inner_item_width
{
545 max_width
= inner_item_width
;
555 fn post_comment_alignment(item_max_width
: Option
<usize>, inner_item_len
: usize) -> usize {
556 item_max_width
.unwrap_or(0).saturating_sub(inner_item_len
)
559 pub(crate) struct ListItems
<'a
, I
, F1
, F2
, F3
>
563 snippet_provider
: &'a SnippetProvider
,
568 prev_span_end
: BytePos
,
569 next_span_start
: BytePos
,
575 pub(crate) fn extract_pre_comment(pre_snippet
: &str) -> (Option
<String
>, ListItemCommentStyle
) {
576 let trimmed_pre_snippet
= pre_snippet
.trim();
577 // Both start and end are checked to support keeping a block comment inline with
578 // the item, even if there are preceding line comments, while still supporting
579 // a snippet that starts with a block comment but also contains one or more
580 // trailing single line comments.
581 // https://github.com/rust-lang/rustfmt/issues/3025
582 // https://github.com/rust-lang/rustfmt/pull/3048
583 // https://github.com/rust-lang/rustfmt/issues/3839
584 let starts_with_block_comment
= trimmed_pre_snippet
.starts_with("/*");
585 let ends_with_block_comment
= trimmed_pre_snippet
.ends_with("*/");
586 let starts_with_single_line_comment
= trimmed_pre_snippet
.starts_with("//");
587 if ends_with_block_comment
{
588 let comment_end
= pre_snippet
.rfind(|c
| c
== '
/'
).unwrap();
589 if pre_snippet
[comment_end
..].contains('
\n'
) {
591 Some(trimmed_pre_snippet
.to_owned()),
592 ListItemCommentStyle
::DifferentLine
,
596 Some(trimmed_pre_snippet
.to_owned()),
597 ListItemCommentStyle
::SameLine
,
600 } else if starts_with_single_line_comment
|| starts_with_block_comment
{
602 Some(trimmed_pre_snippet
.to_owned()),
603 ListItemCommentStyle
::DifferentLine
,
606 (None
, ListItemCommentStyle
::None
)
610 pub(crate) fn extract_post_comment(
615 ) -> Option
<String
> {
616 let white_space
: &[_
] = &[' '
, '
\t'
];
618 // Cleanup post-comment: strip separators and whitespace.
619 let post_snippet
= post_snippet
[..comment_end
].trim();
621 let last_inline_comment_ends_with_separator
= if is_last
{
622 if let Some(line
) = post_snippet
.lines().last() {
623 line
.ends_with(separator
) && line
.trim().starts_with("//")
631 let post_snippet_trimmed
= if post_snippet
.starts_with(|c
| c
== '
,'
|| c
== '
:'
) {
632 post_snippet
[1..].trim_matches(white_space
)
633 } else if let Some(stripped
) = post_snippet
.strip_prefix(separator
) {
634 stripped
.trim_matches(white_space
)
635 } else if last_inline_comment_ends_with_separator
{
636 // since we're on the last item it's fine to keep any trailing separators in comments
637 post_snippet
.trim_matches(white_space
)
639 // not comment or over two lines
640 else if post_snippet
.ends_with('
,'
)
641 && (!post_snippet
.trim().starts_with("//") || post_snippet
.trim().contains('
\n'
))
643 post_snippet
[..(post_snippet
.len() - 1)].trim_matches(white_space
)
647 // FIXME(#3441): post_snippet includes 'const' now
648 // it should not include here
649 let removed_newline_snippet
= post_snippet_trimmed
.trim();
650 if !post_snippet_trimmed
.is_empty()
651 && (removed_newline_snippet
.starts_with("//") || removed_newline_snippet
.starts_with("/*"))
653 Some(post_snippet_trimmed
.to_owned())
659 pub(crate) fn get_comment_end(
667 .find_uncommented(terminator
)
668 .unwrap_or_else(|| post_snippet
.len());
671 let mut block_open_index
= post_snippet
.find("/*");
672 // check if it really is a block comment (and not `//*` or a nested comment)
673 if let Some(i
) = block_open_index
{
674 match post_snippet
.find('
/'
) {
675 Some(j
) if j
< i
=> block_open_index
= None
,
676 _
if post_snippet
[..i
].ends_with('
/'
) => block_open_index
= None
,
680 let newline_index
= post_snippet
.find('
\n'
);
681 if let Some(separator_index
) = post_snippet
.find_uncommented(separator
) {
682 match (block_open_index
, newline_index
) {
683 // Separator before comment, with the next item on same line.
684 // Comment belongs to next item.
685 (Some(i
), None
) if i
> separator_index
=> separator_index
+ 1,
686 // Block-style post-comment before the separator.
687 (Some(i
), None
) => cmp
::max(
688 find_comment_end(&post_snippet
[i
..]).unwrap() + i
,
691 // Block-style post-comment. Either before or after the separator.
692 (Some(i
), Some(j
)) if i
< j
=> cmp
::max(
693 find_comment_end(&post_snippet
[i
..]).unwrap() + i
,
696 // Potential *single* line comment.
697 (_
, Some(j
)) if j
> separator_index
=> j
+ 1,
698 _
=> post_snippet
.len(),
700 } else if let Some(newline_index
) = newline_index
{
701 // Match arms may not have trailing comma. In any case, for match arms,
702 // we will assume that the post comment belongs to the next arm if they
703 // do not end with trailing comma.
710 // Account for extra whitespace between items. This is fiddly
711 // because of the way we divide pre- and post- comments.
712 pub(crate) fn has_extra_newline(post_snippet
: &str, comment_end
: usize) -> bool
{
713 if post_snippet
.is_empty() || comment_end
== 0 {
717 let len_last
= post_snippet
[..comment_end
]
722 // Everything from the separator to the next item.
723 let test_snippet
= &post_snippet
[comment_end
- len_last
..];
724 let first_newline
= test_snippet
726 .unwrap_or_else(|| test_snippet
.len());
727 // From the end of the first line of comments.
728 let test_snippet
= &test_snippet
[first_newline
..];
729 let first
= test_snippet
730 .find(|c
: char| !c
.is_whitespace())
731 .unwrap_or_else(|| test_snippet
.len());
732 // From the end of the first line of comments to the next non-whitespace char.
733 let test_snippet
= &test_snippet
[..first
];
735 // There were multiple line breaks which got trimmed to nothing.
736 count_newlines(test_snippet
) > 1
739 impl<'a
, T
, I
, F1
, F2
, F3
> Iterator
for ListItems
<'a
, I
, F1
, F2
, F3
>
741 I
: Iterator
<Item
= T
>,
742 F1
: Fn(&T
) -> BytePos
,
743 F2
: Fn(&T
) -> BytePos
,
744 F3
: Fn(&T
) -> Option
<String
>,
746 type Item
= ListItem
;
748 fn next(&mut self) -> Option
<Self::Item
> {
749 self.inner
.next().map(|item
| {
751 let pre_snippet
= self
753 .span_to_snippet(mk_sp(self.prev_span_end
, (self.get_lo
)(&item
)))
755 let (pre_comment
, pre_comment_style
) = extract_pre_comment(pre_snippet
);
758 let next_start
= match self.inner
.peek() {
759 Some(next_item
) => (self.get_lo
)(next_item
),
760 None
=> self.next_span_start
,
762 let post_snippet
= self
764 .span_to_snippet(mk_sp((self.get_hi
)(&item
), next_start
))
766 let is_last
= self.inner
.peek().is_none();
768 get_comment_end(post_snippet
, self.separator
, self.terminator
, is_last
);
769 let new_lines
= has_extra_newline(post_snippet
, comment_end
);
771 extract_post_comment(post_snippet
, comment_end
, self.separator
, is_last
);
773 self.prev_span_end
= (self.get_hi
)(&item
) + BytePos(comment_end
as u32);
778 item
: if self.inner
.peek().is_none() && self.leave_last
{
781 (self.get_item_string
)(&item
)
790 #[allow(clippy::too_many_arguments)]
791 // Creates an iterator over a list's items with associated comments.
792 pub(crate) fn itemize_list
<'a
, T
, I
, F1
, F2
, F3
>(
793 snippet_provider
: &'a SnippetProvider
,
800 prev_span_end
: BytePos
,
801 next_span_start
: BytePos
,
803 ) -> ListItems
<'a
, I
, F1
, F2
, F3
>
805 I
: Iterator
<Item
= T
>,
806 F1
: Fn(&T
) -> BytePos
,
807 F2
: Fn(&T
) -> BytePos
,
808 F3
: Fn(&T
) -> Option
<String
>,
812 inner
: inner
.peekable(),
824 /// Returns the count and total width of the list items.
825 fn calculate_width
<I
, T
>(items
: I
) -> (usize, usize)
827 I
: IntoIterator
<Item
= T
>,
832 .map(|item
| total_item_width(item
.as_ref()))
833 .fold((0, 0), |acc
, l
| (acc
.0 + 1, acc
.1 + l
))
836 pub(crate) fn total_item_width(item
: &ListItem
) -> usize {
837 comment_len(item
.pre_comment
.as_ref().map(|x
| &(*x
)[..]))
838 + comment_len(item
.post_comment
.as_ref().map(|x
| &(*x
)[..]))
839 + item
.item
.as_ref().map_or(0, |s
| unicode_str_width(s
))
842 fn comment_len(comment
: Option
<&str>) -> usize {
845 let text_len
= s
.trim().len();
847 // We'll put " /*" before and " */" after inline comments.
857 // Compute horizontal and vertical shapes for a struct-lit-like thing.
858 pub(crate) fn struct_lit_shape(
860 context
: &RewriteContext
<'_
>,
863 ) -> Option
<(Option
<Shape
>, Shape
)> {
864 let v_shape
= match context
.config
.indent_style() {
865 IndentStyle
::Visual
=> shape
867 .shrink_left(prefix_width
)?
868 .sub_width(suffix_width
)?
,
869 IndentStyle
::Block
=> {
870 let shape
= shape
.block_indent(context
.config
.tab_spaces());
872 width
: context
.budget(shape
.indent
.width()),
877 let shape_width
= shape
.width
.checked_sub(prefix_width
+ suffix_width
);
878 if let Some(w
) = shape_width
{
879 let shape_width
= cmp
::min(w
, context
.config
.struct_lit_width());
880 Some((Some(Shape
::legacy(shape_width
, shape
.indent
)), v_shape
))
882 Some((None
, v_shape
))
886 // Compute the tactic for the internals of a struct-lit-like thing.
887 pub(crate) fn struct_lit_tactic(
888 h_shape
: Option
<Shape
>,
889 context
: &RewriteContext
<'_
>,
891 ) -> DefinitiveListTactic
{
892 if let Some(h_shape
) = h_shape
{
893 let prelim_tactic
= match (context
.config
.indent_style(), items
.len()) {
894 (IndentStyle
::Visual
, 1) => ListTactic
::HorizontalVertical
,
895 _
if context
.config
.struct_lit_single_line() => ListTactic
::HorizontalVertical
,
896 _
=> ListTactic
::Vertical
,
898 definitive_tactic(items
, prelim_tactic
, Separator
::Comma
, h_shape
.width
)
900 DefinitiveListTactic
::Vertical
904 // Given a tactic and possible shapes for horizontal and vertical layout,
905 // come up with the actual shape to use.
906 pub(crate) fn shape_for_tactic(
907 tactic
: DefinitiveListTactic
,
908 h_shape
: Option
<Shape
>,
912 DefinitiveListTactic
::Horizontal
=> h_shape
.unwrap(),
917 // Create a ListFormatting object for formatting the internals of a
918 // struct-lit-like thing, that is a series of fields.
919 pub(crate) fn struct_lit_formatting
<'a
>(
921 tactic
: DefinitiveListTactic
,
922 context
: &'a RewriteContext
<'_
>,
923 force_no_trailing_comma
: bool
,
924 ) -> ListFormatting
<'a
> {
925 let ends_with_newline
= context
.config
.indent_style() != IndentStyle
::Visual
926 && tactic
== DefinitiveListTactic
::Vertical
;
930 trailing_separator
: if force_no_trailing_comma
{
931 SeparatorTactic
::Never
933 context
.config
.trailing_comma()
935 separator_place
: SeparatorPlace
::Back
,
938 preserve_newline
: true,
940 align_comments
: true,
941 config
: context
.config
,