1use std::cmp;
4use std::iter::Peekable;
5
6use rustc_span::{BytePos, Span};
7
8use crate::comment::{FindUncommented, find_comment_end, rewrite_comment};
9use crate::config::lists::*;
10use crate::config::{Config, IndentStyle};
11use crate::rewrite::{ExceedsMaxWidthError, RewriteContext, RewriteError, RewriteResult};
12use crate::shape::{Indent, Shape};
13use crate::utils::{
14 count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline,
15 unicode_str_width,
16};
17use crate::visitor::SnippetProvider;
18
19pub(crate) struct ListFormatting<'a> {
20 tactic: DefinitiveListTactic,
21 separator: &'a str,
22 trailing_separator: SeparatorTactic,
23 separator_place: SeparatorPlace,
24 shape: Shape,
25 ends_with_newline: bool,
28 preserve_newline: bool,
30 nested: bool,
32 align_comments: bool,
34 config: &'a Config,
35}
36
37impl<'a> ListFormatting<'a> {
38 pub(crate) fn new(shape: Shape, config: &'a Config) -> Self {
39 ListFormatting {
40 tactic: DefinitiveListTactic::Vertical,
41 separator: ",",
42 trailing_separator: SeparatorTactic::Never,
43 separator_place: SeparatorPlace::Back,
44 shape,
45 ends_with_newline: true,
46 preserve_newline: false,
47 nested: false,
48 align_comments: true,
49 config,
50 }
51 }
52
53 pub(crate) fn tactic(mut self, tactic: DefinitiveListTactic) -> Self {
54 self.tactic = tactic;
55 self
56 }
57
58 pub(crate) fn separator(mut self, separator: &'a str) -> Self {
59 self.separator = separator;
60 self
61 }
62
63 pub(crate) fn trailing_separator(mut self, trailing_separator: SeparatorTactic) -> Self {
64 self.trailing_separator = trailing_separator;
65 self
66 }
67
68 pub(crate) fn separator_place(mut self, separator_place: SeparatorPlace) -> Self {
69 self.separator_place = separator_place;
70 self
71 }
72
73 pub(crate) fn ends_with_newline(mut self, ends_with_newline: bool) -> Self {
74 self.ends_with_newline = ends_with_newline;
75 self
76 }
77
78 pub(crate) fn preserve_newline(mut self, preserve_newline: bool) -> Self {
79 self.preserve_newline = preserve_newline;
80 self
81 }
82
83 pub(crate) fn nested(mut self, nested: bool) -> Self {
84 self.nested = nested;
85 self
86 }
87
88 pub(crate) fn align_comments(mut self, align_comments: bool) -> Self {
89 self.align_comments = align_comments;
90 self
91 }
92
93 pub(crate) fn needs_trailing_separator(&self) -> bool {
94 match self.trailing_separator {
95 SeparatorTactic::Always => true,
97 SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical,
98 SeparatorTactic::Never => {
99 self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front()
100 }
101 }
102 }
103}
104
105impl AsRef<ListItem> for ListItem {
106 fn as_ref(&self) -> &ListItem {
107 self
108 }
109}
110
111#[derive(PartialEq, Eq, Debug, Copy, Clone)]
112pub(crate) enum ListItemCommentStyle {
113 SameLine,
115 DifferentLine,
117 None,
119}
120
121#[derive(Debug, Clone)]
122pub(crate) struct ListItem {
123 pub(crate) pre_comment: Option<String>,
125 pub(crate) pre_comment_style: ListItemCommentStyle,
126 pub(crate) item: RewriteResult,
129 pub(crate) post_comment: Option<String>,
130 pub(crate) new_lines: bool,
132}
133
134impl ListItem {
135 pub(crate) fn from_item(item: RewriteResult) -> ListItem {
136 ListItem {
137 pre_comment: None,
138 pre_comment_style: ListItemCommentStyle::None,
139 item: item,
140 post_comment: None,
141 new_lines: false,
142 }
143 }
144
145 pub(crate) fn inner_as_ref(&self) -> &str {
146 self.item.as_ref().map_or("", |s| s)
147 }
148
149 pub(crate) fn is_different_group(&self) -> bool {
150 self.inner_as_ref().contains('\n')
151 || self.pre_comment.is_some()
152 || self
153 .post_comment
154 .as_ref()
155 .map_or(false, |s| s.contains('\n'))
156 }
157
158 pub(crate) fn is_multiline(&self) -> bool {
159 self.inner_as_ref().contains('\n')
160 || self
161 .pre_comment
162 .as_ref()
163 .map_or(false, |s| s.contains('\n'))
164 || self
165 .post_comment
166 .as_ref()
167 .map_or(false, |s| s.contains('\n'))
168 }
169
170 pub(crate) fn has_single_line_comment(&self) -> bool {
171 self.pre_comment
172 .as_ref()
173 .map_or(false, |comment| comment.trim_start().starts_with("//"))
174 || self
175 .post_comment
176 .as_ref()
177 .map_or(false, |comment| comment.trim_start().starts_with("//"))
178 }
179
180 pub(crate) fn has_comment(&self) -> bool {
181 self.pre_comment.is_some() || self.post_comment.is_some()
182 }
183
184 pub(crate) fn from_str<S: Into<String>>(s: S) -> ListItem {
185 ListItem {
186 pre_comment: None,
187 pre_comment_style: ListItemCommentStyle::None,
188 item: Ok(s.into()),
189 post_comment: None,
190 new_lines: false,
191 }
192 }
193
194 fn is_substantial(&self) -> bool {
196 fn empty(s: &Option<String>) -> bool {
197 !matches!(*s, Some(ref s) if !s.is_empty())
198 }
199
200 fn empty_result(s: &RewriteResult) -> bool {
201 !matches!(*s, Ok(ref s) if !s.is_empty())
202 }
203
204 !(empty(&self.pre_comment) && empty_result(&self.item) && empty(&self.post_comment))
205 }
206}
207
208#[derive(Copy, Clone, Eq, PartialEq, Debug)]
210pub(crate) enum Separator {
211 Comma,
212 VerticalBar,
213}
214
215impl Separator {
216 pub(crate) fn len(self) -> usize {
217 match self {
218 Separator::Comma => 2,
220 Separator::VerticalBar => 3,
222 }
223 }
224}
225
226pub(crate) fn definitive_tactic<I, T>(
227 items: I,
228 tactic: ListTactic,
229 sep: Separator,
230 width: usize,
231) -> DefinitiveListTactic
232where
233 I: IntoIterator<Item = T> + Clone,
234 T: AsRef<ListItem>,
235{
236 let pre_line_comments = items
237 .clone()
238 .into_iter()
239 .any(|item| item.as_ref().has_single_line_comment());
240
241 let limit = match tactic {
242 _ if pre_line_comments => return DefinitiveListTactic::Vertical,
243 ListTactic::Horizontal => return DefinitiveListTactic::Horizontal,
244 ListTactic::Vertical => return DefinitiveListTactic::Vertical,
245 ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit),
246 ListTactic::Mixed | ListTactic::HorizontalVertical => width,
247 };
248
249 let (sep_count, total_width) = calculate_width(items.clone());
250 let total_sep_len = sep.len() * sep_count.saturating_sub(1);
251 let real_total = total_width + total_sep_len;
252
253 if real_total <= limit && !items.into_iter().any(|item| item.as_ref().is_multiline()) {
254 DefinitiveListTactic::Horizontal
255 } else {
256 match tactic {
257 ListTactic::Mixed => DefinitiveListTactic::Mixed,
258 _ => DefinitiveListTactic::Vertical,
259 }
260 }
261}
262
263pub(crate) fn write_list<I, T>(items: I, formatting: &ListFormatting<'_>) -> RewriteResult
265where
266 I: IntoIterator<Item = T> + Clone,
267 T: AsRef<ListItem>,
268{
269 let tactic = formatting.tactic;
270 let sep_len = formatting.separator.len();
271
272 let mut trailing_separator = formatting.needs_trailing_separator();
275 let mut result = String::with_capacity(128);
276 let cloned_items = items.clone();
277 let mut iter = items.into_iter().enumerate().peekable();
278 let mut item_max_width: Option<usize> = None;
279 let sep_place =
280 SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator);
281 let mut prev_item_had_post_comment = false;
282 let mut prev_item_is_nested_import = false;
283
284 let mut line_len = 0;
285 let indent_str = &formatting.shape.indent.to_string(formatting.config);
286 while let Some((i, item)) = iter.next() {
287 let item = item.as_ref();
288 let inner_item = item.item.as_ref().or_else(|err| Err(err.clone()))?;
289 let first = i == 0;
290 let last = iter.peek().is_none();
291 let mut separate = match sep_place {
292 SeparatorPlace::Front => !first,
293 SeparatorPlace::Back => !last || trailing_separator,
294 };
295 let item_sep_len = if separate { sep_len } else { 0 };
296
297 let item_last_line = if item.is_multiline() {
300 inner_item.lines().last().unwrap_or("")
301 } else {
302 inner_item.as_ref()
303 };
304 let mut item_last_line_width = unicode_str_width(item_last_line) + item_sep_len;
305 if item_last_line.starts_with(&**indent_str) {
306 item_last_line_width -= unicode_str_width(indent_str);
307 }
308
309 if !item.is_substantial() {
310 continue;
311 }
312
313 match tactic {
314 DefinitiveListTactic::Horizontal if !first => {
315 result.push(' ');
316 }
317 DefinitiveListTactic::SpecialMacro(num_args_before) => {
318 if i == 0 {
319 } else if i < num_args_before {
321 result.push(' ');
322 } else if i <= num_args_before + 1 {
323 result.push('\n');
324 result.push_str(indent_str);
325 } else {
326 result.push(' ');
327 }
328 }
329 DefinitiveListTactic::Vertical
330 if !first && !inner_item.is_empty() && !result.is_empty() =>
331 {
332 result.push('\n');
333 result.push_str(indent_str);
334 }
335 DefinitiveListTactic::Mixed => {
336 let total_width = total_item_width(item) + item_sep_len;
337
338 if (line_len > 0 && line_len + 1 + total_width > formatting.shape.width)
340 || prev_item_had_post_comment
341 || (formatting.nested
342 && (prev_item_is_nested_import || (!first && inner_item.contains("::"))))
343 {
344 result.push('\n');
345 result.push_str(indent_str);
346 line_len = 0;
347 if formatting.ends_with_newline {
348 trailing_separator = true;
349 }
350 } else if line_len > 0 {
351 result.push(' ');
352 line_len += 1;
353 }
354
355 if last && formatting.ends_with_newline {
356 separate = formatting.trailing_separator != SeparatorTactic::Never;
357 }
358
359 line_len += total_width;
360 }
361 _ => {}
362 }
363
364 if let Some(ref comment) = item.pre_comment {
366 let block_mode = tactic == DefinitiveListTactic::Horizontal;
368 let comment =
370 rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?;
371 result.push_str(&comment);
372
373 if !inner_item.is_empty() {
374 use DefinitiveListTactic::*;
375 if matches!(tactic, Vertical | Mixed | SpecialMacro(_)) {
376 let keep_comment = if formatting.config.normalize_comments()
378 || item.pre_comment_style == ListItemCommentStyle::DifferentLine
379 {
380 false
381 } else {
382 let total_width = total_item_width(item) + item_sep_len + 1;
385 total_width <= formatting.shape.width
386 };
387 if keep_comment {
388 result.push(' ');
389 } else {
390 result.push('\n');
391 result.push_str(indent_str);
392 line_len = item.item.as_ref().map_or(0, |s| unicode_str_width(s));
394 }
395 } else {
396 result.push(' ')
397 }
398 }
399 item_max_width = None;
400 }
401
402 if separate && sep_place.is_front() && !first {
403 result.push_str(formatting.separator.trim());
404 result.push(' ');
405 }
406 result.push_str(inner_item);
407
408 if tactic == DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
410 let comment = item.post_comment.as_ref().unwrap();
411 let formatted_comment = rewrite_comment(
412 comment,
413 true,
414 Shape::legacy(formatting.shape.width, Indent::empty()),
415 formatting.config,
416 )?;
417
418 result.push(' ');
419 result.push_str(&formatted_comment);
420 }
421
422 if separate && sep_place.is_back() {
423 result.push_str(formatting.separator);
424 }
425
426 if tactic != DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
427 let comment = item.post_comment.as_ref().unwrap();
428 let overhead = last_line_width(&result, formatting.config.tab_spaces())
429 + first_line_width(comment.trim());
430
431 let rewrite_post_comment = |item_max_width: &mut Option<usize>| {
432 if item_max_width.is_none() && !last && !inner_item.contains('\n') {
433 *item_max_width = Some(max_width_of_item_with_post_comment(
434 &cloned_items,
435 i,
436 overhead,
437 formatting.config.max_width(),
438 ));
439 }
440 let overhead = if starts_with_newline(comment) {
441 0
442 } else if let Some(max_width) = *item_max_width {
443 max_width + 2
444 } else {
445 item_last_line_width + 1
447 };
448 let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1);
449 let offset = formatting.shape.indent + overhead;
450 let comment_shape = Shape::legacy(width, offset);
451
452 let block_style = if !formatting.ends_with_newline && last {
453 true
454 } else if starts_with_newline(comment) {
455 false
456 } else {
457 comment.trim().contains('\n') || unicode_str_width(comment.trim()) > width
458 };
459
460 rewrite_comment(
461 comment.trim_start(),
462 block_style,
463 comment_shape,
464 formatting.config,
465 )
466 };
467
468 let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
469
470 if !starts_with_newline(comment) {
471 if formatting.align_comments {
472 let mut comment_alignment =
473 post_comment_alignment(item_max_width, unicode_str_width(inner_item));
474 if first_line_width(&formatted_comment)
475 + last_line_width(&result, formatting.config.tab_spaces())
476 + comment_alignment
477 + 1
478 > formatting.config.max_width()
479 {
480 item_max_width = None;
481 formatted_comment = rewrite_post_comment(&mut item_max_width)?;
482 comment_alignment =
483 post_comment_alignment(item_max_width, unicode_str_width(inner_item));
484 }
485 for _ in 0..=comment_alignment {
486 result.push(' ');
487 }
488 }
489 if !formatting.align_comments
492 || (last
493 && item_max_width.is_some()
494 && !separate
495 && !formatting.separator.is_empty())
496 {
497 result.push(' ');
498 }
499 } else {
500 result.push('\n');
501 result.push_str(indent_str);
502 }
503 if formatted_comment.contains('\n') {
504 item_max_width = None;
505 }
506 result.push_str(&formatted_comment);
507 } else {
508 item_max_width = None;
509 }
510
511 if formatting.preserve_newline
512 && !last
513 && tactic == DefinitiveListTactic::Vertical
514 && item.new_lines
515 {
516 item_max_width = None;
517 result.push('\n');
518 }
519
520 prev_item_had_post_comment = item.post_comment.is_some();
521 prev_item_is_nested_import = inner_item.contains("::");
522 }
523
524 Ok(result)
525}
526
527fn max_width_of_item_with_post_comment<I, T>(
528 items: &I,
529 i: usize,
530 overhead: usize,
531 max_budget: usize,
532) -> usize
533where
534 I: IntoIterator<Item = T> + Clone,
535 T: AsRef<ListItem>,
536{
537 let mut max_width = 0;
538 let mut first = true;
539 for item in items.clone().into_iter().skip(i) {
540 let item = item.as_ref();
541 let inner_item_width = unicode_str_width(item.inner_as_ref());
542 if !first
543 && (item.is_different_group()
544 || item.post_comment.is_none()
545 || inner_item_width + overhead > max_budget)
546 {
547 return max_width;
548 }
549 if max_width < inner_item_width {
550 max_width = inner_item_width;
551 }
552 if item.new_lines {
553 return max_width;
554 }
555 first = false;
556 }
557 max_width
558}
559
560fn post_comment_alignment(item_max_width: Option<usize>, inner_item_width: usize) -> usize {
561 item_max_width.unwrap_or(0).saturating_sub(inner_item_width)
562}
563
564pub(crate) struct ListItems<'a, I, F1, F2, F3>
565where
566 I: Iterator,
567{
568 snippet_provider: &'a SnippetProvider,
569 inner: Peekable<I>,
570 get_lo: F1,
571 get_hi: F2,
572 get_item_string: F3,
573 prev_span_end: BytePos,
574 next_span_start: BytePos,
575 terminator: &'a str,
576 separator: &'a str,
577 leave_last: bool,
578}
579
580pub(crate) fn extract_pre_comment(pre_snippet: &str) -> (Option<String>, ListItemCommentStyle) {
581 let trimmed_pre_snippet = pre_snippet.trim();
582 let starts_with_block_comment = trimmed_pre_snippet.starts_with("/*");
590 let ends_with_block_comment = trimmed_pre_snippet.ends_with("*/");
591 let starts_with_single_line_comment = trimmed_pre_snippet.starts_with("//");
592 if ends_with_block_comment {
593 let comment_end = pre_snippet.rfind(|c| c == '/').unwrap();
594 if pre_snippet[comment_end..].contains('\n') {
595 (
596 Some(trimmed_pre_snippet.to_owned()),
597 ListItemCommentStyle::DifferentLine,
598 )
599 } else {
600 (
601 Some(trimmed_pre_snippet.to_owned()),
602 ListItemCommentStyle::SameLine,
603 )
604 }
605 } else if starts_with_single_line_comment || starts_with_block_comment {
606 (
607 Some(trimmed_pre_snippet.to_owned()),
608 ListItemCommentStyle::DifferentLine,
609 )
610 } else {
611 (None, ListItemCommentStyle::None)
612 }
613}
614
615pub(crate) fn extract_post_comment(
616 post_snippet: &str,
617 comment_end: usize,
618 separator: &str,
619 is_last: bool,
620) -> Option<String> {
621 let white_space: &[_] = &[' ', '\t'];
622
623 let post_snippet = post_snippet[..comment_end].trim();
625
626 let last_inline_comment_ends_with_separator = if is_last {
627 if let Some(line) = post_snippet.lines().last() {
628 line.ends_with(separator) && line.trim().starts_with("//")
629 } else {
630 false
631 }
632 } else {
633 false
634 };
635
636 let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
637 post_snippet[1..].trim_matches(white_space)
638 } else if let Some(stripped) = post_snippet.strip_prefix(separator) {
639 stripped.trim_matches(white_space)
640 } else if last_inline_comment_ends_with_separator {
641 post_snippet.trim_matches(white_space)
643 }
644 else if post_snippet.ends_with(separator)
646 && (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n'))
647 {
648 post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
649 } else {
650 post_snippet
651 };
652 let removed_newline_snippet = post_snippet_trimmed.trim();
655 if !post_snippet_trimmed.is_empty()
656 && (removed_newline_snippet.starts_with("//") || removed_newline_snippet.starts_with("/*"))
657 {
658 Some(post_snippet_trimmed.to_owned())
659 } else {
660 None
661 }
662}
663
664pub(crate) fn get_comment_end(
665 post_snippet: &str,
666 separator: &str,
667 terminator: &str,
668 is_last: bool,
669) -> usize {
670 if is_last {
671 return post_snippet
672 .find_uncommented(terminator)
673 .unwrap_or_else(|| post_snippet.len());
674 }
675
676 let mut block_open_index = post_snippet.find("/*");
677 if let Some(i) = block_open_index {
679 match post_snippet.find('/') {
680 Some(j) if j < i => block_open_index = None,
681 _ if post_snippet[..i].ends_with('/') => block_open_index = None,
682 _ => (),
683 }
684 }
685 let newline_index = post_snippet.find('\n');
686 if let Some(separator_index) = post_snippet.find_uncommented(separator) {
687 match (block_open_index, newline_index) {
688 (Some(i), None) if i > separator_index => separator_index + 1,
691 (Some(i), None) => cmp::max(
693 find_comment_end(&post_snippet[i..]).unwrap() + i,
694 separator_index + 1,
695 ),
696 (Some(i), Some(j)) if i < j => cmp::max(
698 find_comment_end(&post_snippet[i..]).unwrap() + i,
699 separator_index + 1,
700 ),
701 (_, Some(j)) if j > separator_index => j + 1,
703 _ => post_snippet.len(),
704 }
705 } else if let Some(newline_index) = newline_index {
706 newline_index + 1
710 } else {
711 0
712 }
713}
714
715pub(crate) fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool {
718 if post_snippet.is_empty() || comment_end == 0 {
719 return false;
720 }
721
722 let len_last = post_snippet[..comment_end]
723 .chars()
724 .last()
725 .unwrap()
726 .len_utf8();
727 let test_snippet = &post_snippet[comment_end - len_last..];
729 let first_newline = test_snippet
730 .find('\n')
731 .unwrap_or_else(|| test_snippet.len());
732 let test_snippet = &test_snippet[first_newline..];
734 let first = test_snippet
735 .find(|c: char| !c.is_whitespace())
736 .unwrap_or_else(|| test_snippet.len());
737 let test_snippet = &test_snippet[..first];
739
740 count_newlines(test_snippet) > 1
742}
743
744impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
745where
746 I: Iterator<Item = T>,
747 F1: Fn(&T) -> BytePos,
748 F2: Fn(&T) -> BytePos,
749 F3: Fn(&T) -> RewriteResult,
750{
751 type Item = ListItem;
752
753 fn next(&mut self) -> Option<Self::Item> {
754 self.inner.next().map(|item| {
755 let pre_snippet = self
757 .snippet_provider
758 .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
759 .unwrap_or("");
760 let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet);
761
762 let next_start = match self.inner.peek() {
764 Some(next_item) => (self.get_lo)(next_item),
765 None => self.next_span_start,
766 };
767 let post_snippet = self
768 .snippet_provider
769 .span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
770 .unwrap_or("");
771 let is_last = self.inner.peek().is_none();
772 let comment_end =
773 get_comment_end(post_snippet, self.separator, self.terminator, is_last);
774 let new_lines = has_extra_newline(post_snippet, comment_end);
775 let post_comment =
776 extract_post_comment(post_snippet, comment_end, self.separator, is_last);
777
778 self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
779
780 ListItem {
781 pre_comment,
782 pre_comment_style,
783 item: if self.inner.peek().is_none() && self.leave_last {
785 Err(RewriteError::SkipFormatting)
786 } else {
787 (self.get_item_string)(&item)
788 },
789 post_comment,
790 new_lines,
791 }
792 })
793 }
794}
795
796#[allow(clippy::too_many_arguments)]
797pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>(
799 snippet_provider: &'a SnippetProvider,
800 inner: I,
801 terminator: &'a str,
802 separator: &'a str,
803 get_lo: F1,
804 get_hi: F2,
805 get_item_string: F3,
806 prev_span_end: BytePos,
807 next_span_start: BytePos,
808 leave_last: bool,
809) -> ListItems<'a, I, F1, F2, F3>
810where
811 I: Iterator<Item = T>,
812 F1: Fn(&T) -> BytePos,
813 F2: Fn(&T) -> BytePos,
814 F3: Fn(&T) -> RewriteResult,
815{
816 ListItems {
817 snippet_provider,
818 inner: inner.peekable(),
819 get_lo,
820 get_hi,
821 get_item_string,
822 prev_span_end,
823 next_span_start,
824 terminator,
825 separator,
826 leave_last,
827 }
828}
829
830fn calculate_width<I, T>(items: I) -> (usize, usize)
832where
833 I: IntoIterator<Item = T>,
834 T: AsRef<ListItem>,
835{
836 items
837 .into_iter()
838 .map(|item| total_item_width(item.as_ref()))
839 .fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l))
840}
841
842pub(crate) fn total_item_width(item: &ListItem) -> usize {
843 comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..]))
844 + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..]))
845 + item.item.as_ref().map_or(0, |s| unicode_str_width(s))
846}
847
848fn comment_len(comment: Option<&str>) -> usize {
849 match comment {
850 Some(s) => {
851 let text_len = s.trim().len();
852 if text_len > 0 {
853 text_len + 6
855 } else {
856 text_len
857 }
858 }
859 None => 0,
860 }
861}
862
863pub(crate) fn struct_lit_shape(
865 shape: Shape,
866 context: &RewriteContext<'_>,
867 prefix_width: usize,
868 suffix_width: usize,
869 span: Span,
870) -> Result<(Option<Shape>, Shape), ExceedsMaxWidthError> {
871 let v_shape = match context.config.indent_style() {
872 IndentStyle::Visual => shape
873 .visual_indent(0)
874 .shrink_left(prefix_width, span)?
875 .sub_width(suffix_width, span)?,
876 IndentStyle::Block => {
877 let shape = shape.block_indent(context.config.tab_spaces());
878 Shape {
879 width: context.budget(shape.indent.width()),
880 ..shape
881 }
882 }
883 };
884 let h_shape = shape
885 .width
886 .checked_sub(prefix_width + suffix_width)
887 .map(|w| {
888 let shape_width = cmp::min(w, context.config.struct_lit_width());
889 Shape::legacy(shape_width, shape.indent)
890 });
891 Ok((h_shape, v_shape))
892}
893
894pub(crate) fn struct_lit_tactic(
896 h_shape: Option<Shape>,
897 context: &RewriteContext<'_>,
898 items: &[ListItem],
899) -> DefinitiveListTactic {
900 if let Some(h_shape) = h_shape {
901 let prelim_tactic = match (context.config.indent_style(), items.len()) {
902 (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
903 _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
904 _ => ListTactic::Vertical,
905 };
906 definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
907 } else {
908 DefinitiveListTactic::Vertical
909 }
910}
911
912pub(crate) fn shape_for_tactic(
915 tactic: DefinitiveListTactic,
916 h_shape: Option<Shape>,
917 v_shape: Shape,
918) -> Shape {
919 match tactic {
920 DefinitiveListTactic::Horizontal => h_shape.unwrap(),
921 _ => v_shape,
922 }
923}
924
925pub(crate) fn struct_lit_formatting<'a>(
928 shape: Shape,
929 tactic: DefinitiveListTactic,
930 context: &'a RewriteContext<'_>,
931 force_no_trailing_comma: bool,
932) -> ListFormatting<'a> {
933 let ends_with_newline = context.config.indent_style() != IndentStyle::Visual
934 && tactic == DefinitiveListTactic::Vertical;
935 ListFormatting {
936 tactic,
937 separator: ",",
938 trailing_separator: if force_no_trailing_comma {
939 SeparatorTactic::Never
940 } else {
941 context.config.trailing_comma()
942 },
943 separator_place: SeparatorPlace::Back,
944 shape,
945 ends_with_newline,
946 preserve_newline: true,
947 nested: false,
948 align_comments: true,
949 config: context.config,
950 }
951}