1use std::cmp;
4use std::iter::Peekable;
5
6use rustc_span::BytePos;
7
8use crate::comment::{FindUncommented, find_comment_end, rewrite_comment};
9use crate::config::lists::*;
10use crate::config::{Config, IndentStyle};
11use crate::rewrite::{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) + first_line_width(comment.trim());
429
430 let rewrite_post_comment = |item_max_width: &mut Option<usize>| {
431 if item_max_width.is_none() && !last && !inner_item.contains('\n') {
432 *item_max_width = Some(max_width_of_item_with_post_comment(
433 &cloned_items,
434 i,
435 overhead,
436 formatting.config.max_width(),
437 ));
438 }
439 let overhead = if starts_with_newline(comment) {
440 0
441 } else if let Some(max_width) = *item_max_width {
442 max_width + 2
443 } else {
444 item_last_line_width + 1
446 };
447 let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1);
448 let offset = formatting.shape.indent + overhead;
449 let comment_shape = Shape::legacy(width, offset);
450
451 let block_style = if !formatting.ends_with_newline && last {
452 true
453 } else if starts_with_newline(comment) {
454 false
455 } else {
456 comment.trim().contains('\n') || unicode_str_width(comment.trim()) > width
457 };
458
459 rewrite_comment(
460 comment.trim_start(),
461 block_style,
462 comment_shape,
463 formatting.config,
464 )
465 };
466
467 let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
468
469 if !starts_with_newline(comment) {
470 if formatting.align_comments {
471 let mut comment_alignment =
472 post_comment_alignment(item_max_width, unicode_str_width(inner_item));
473 if first_line_width(&formatted_comment)
474 + last_line_width(&result)
475 + comment_alignment
476 + 1
477 > formatting.config.max_width()
478 {
479 item_max_width = None;
480 formatted_comment = rewrite_post_comment(&mut item_max_width)?;
481 comment_alignment =
482 post_comment_alignment(item_max_width, unicode_str_width(inner_item));
483 }
484 for _ in 0..=comment_alignment {
485 result.push(' ');
486 }
487 }
488 if !formatting.align_comments
491 || (last
492 && item_max_width.is_some()
493 && !separate
494 && !formatting.separator.is_empty())
495 {
496 result.push(' ');
497 }
498 } else {
499 result.push('\n');
500 result.push_str(indent_str);
501 }
502 if formatted_comment.contains('\n') {
503 item_max_width = None;
504 }
505 result.push_str(&formatted_comment);
506 } else {
507 item_max_width = None;
508 }
509
510 if formatting.preserve_newline
511 && !last
512 && tactic == DefinitiveListTactic::Vertical
513 && item.new_lines
514 {
515 item_max_width = None;
516 result.push('\n');
517 }
518
519 prev_item_had_post_comment = item.post_comment.is_some();
520 prev_item_is_nested_import = inner_item.contains("::");
521 }
522
523 Ok(result)
524}
525
526fn max_width_of_item_with_post_comment<I, T>(
527 items: &I,
528 i: usize,
529 overhead: usize,
530 max_budget: usize,
531) -> usize
532where
533 I: IntoIterator<Item = T> + Clone,
534 T: AsRef<ListItem>,
535{
536 let mut max_width = 0;
537 let mut first = true;
538 for item in items.clone().into_iter().skip(i) {
539 let item = item.as_ref();
540 let inner_item_width = unicode_str_width(item.inner_as_ref());
541 if !first
542 && (item.is_different_group()
543 || item.post_comment.is_none()
544 || inner_item_width + overhead > max_budget)
545 {
546 return max_width;
547 }
548 if max_width < inner_item_width {
549 max_width = inner_item_width;
550 }
551 if item.new_lines {
552 return max_width;
553 }
554 first = false;
555 }
556 max_width
557}
558
559fn post_comment_alignment(item_max_width: Option<usize>, inner_item_width: usize) -> usize {
560 item_max_width.unwrap_or(0).saturating_sub(inner_item_width)
561}
562
563pub(crate) struct ListItems<'a, I, F1, F2, F3>
564where
565 I: Iterator,
566{
567 snippet_provider: &'a SnippetProvider,
568 inner: Peekable<I>,
569 get_lo: F1,
570 get_hi: F2,
571 get_item_string: F3,
572 prev_span_end: BytePos,
573 next_span_start: BytePos,
574 terminator: &'a str,
575 separator: &'a str,
576 leave_last: bool,
577}
578
579pub(crate) fn extract_pre_comment(pre_snippet: &str) -> (Option<String>, ListItemCommentStyle) {
580 let trimmed_pre_snippet = pre_snippet.trim();
581 let starts_with_block_comment = trimmed_pre_snippet.starts_with("/*");
589 let ends_with_block_comment = trimmed_pre_snippet.ends_with("*/");
590 let starts_with_single_line_comment = trimmed_pre_snippet.starts_with("//");
591 if ends_with_block_comment {
592 let comment_end = pre_snippet.rfind(|c| c == '/').unwrap();
593 if pre_snippet[comment_end..].contains('\n') {
594 (
595 Some(trimmed_pre_snippet.to_owned()),
596 ListItemCommentStyle::DifferentLine,
597 )
598 } else {
599 (
600 Some(trimmed_pre_snippet.to_owned()),
601 ListItemCommentStyle::SameLine,
602 )
603 }
604 } else if starts_with_single_line_comment || starts_with_block_comment {
605 (
606 Some(trimmed_pre_snippet.to_owned()),
607 ListItemCommentStyle::DifferentLine,
608 )
609 } else {
610 (None, ListItemCommentStyle::None)
611 }
612}
613
614pub(crate) fn extract_post_comment(
615 post_snippet: &str,
616 comment_end: usize,
617 separator: &str,
618 is_last: bool,
619) -> Option<String> {
620 let white_space: &[_] = &[' ', '\t'];
621
622 let post_snippet = post_snippet[..comment_end].trim();
624
625 let last_inline_comment_ends_with_separator = if is_last {
626 if let Some(line) = post_snippet.lines().last() {
627 line.ends_with(separator) && line.trim().starts_with("//")
628 } else {
629 false
630 }
631 } else {
632 false
633 };
634
635 let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
636 post_snippet[1..].trim_matches(white_space)
637 } else if let Some(stripped) = post_snippet.strip_prefix(separator) {
638 stripped.trim_matches(white_space)
639 } else if last_inline_comment_ends_with_separator {
640 post_snippet.trim_matches(white_space)
642 }
643 else if post_snippet.ends_with(separator)
645 && (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n'))
646 {
647 post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
648 } else {
649 post_snippet
650 };
651 let removed_newline_snippet = post_snippet_trimmed.trim();
654 if !post_snippet_trimmed.is_empty()
655 && (removed_newline_snippet.starts_with("//") || removed_newline_snippet.starts_with("/*"))
656 {
657 Some(post_snippet_trimmed.to_owned())
658 } else {
659 None
660 }
661}
662
663pub(crate) fn get_comment_end(
664 post_snippet: &str,
665 separator: &str,
666 terminator: &str,
667 is_last: bool,
668) -> usize {
669 if is_last {
670 return post_snippet
671 .find_uncommented(terminator)
672 .unwrap_or_else(|| post_snippet.len());
673 }
674
675 let mut block_open_index = post_snippet.find("/*");
676 if let Some(i) = block_open_index {
678 match post_snippet.find('/') {
679 Some(j) if j < i => block_open_index = None,
680 _ if post_snippet[..i].ends_with('/') => block_open_index = None,
681 _ => (),
682 }
683 }
684 let newline_index = post_snippet.find('\n');
685 if let Some(separator_index) = post_snippet.find_uncommented(separator) {
686 match (block_open_index, newline_index) {
687 (Some(i), None) if i > separator_index => separator_index + 1,
690 (Some(i), None) => cmp::max(
692 find_comment_end(&post_snippet[i..]).unwrap() + i,
693 separator_index + 1,
694 ),
695 (Some(i), Some(j)) if i < j => cmp::max(
697 find_comment_end(&post_snippet[i..]).unwrap() + i,
698 separator_index + 1,
699 ),
700 (_, Some(j)) if j > separator_index => j + 1,
702 _ => post_snippet.len(),
703 }
704 } else if let Some(newline_index) = newline_index {
705 newline_index + 1
709 } else {
710 0
711 }
712}
713
714pub(crate) fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool {
717 if post_snippet.is_empty() || comment_end == 0 {
718 return false;
719 }
720
721 let len_last = post_snippet[..comment_end]
722 .chars()
723 .last()
724 .unwrap()
725 .len_utf8();
726 let test_snippet = &post_snippet[comment_end - len_last..];
728 let first_newline = test_snippet
729 .find('\n')
730 .unwrap_or_else(|| test_snippet.len());
731 let test_snippet = &test_snippet[first_newline..];
733 let first = test_snippet
734 .find(|c: char| !c.is_whitespace())
735 .unwrap_or_else(|| test_snippet.len());
736 let test_snippet = &test_snippet[..first];
738
739 count_newlines(test_snippet) > 1
741}
742
743impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
744where
745 I: Iterator<Item = T>,
746 F1: Fn(&T) -> BytePos,
747 F2: Fn(&T) -> BytePos,
748 F3: Fn(&T) -> RewriteResult,
749{
750 type Item = ListItem;
751
752 fn next(&mut self) -> Option<Self::Item> {
753 self.inner.next().map(|item| {
754 let pre_snippet = self
756 .snippet_provider
757 .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
758 .unwrap_or("");
759 let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet);
760
761 let next_start = match self.inner.peek() {
763 Some(next_item) => (self.get_lo)(next_item),
764 None => self.next_span_start,
765 };
766 let post_snippet = self
767 .snippet_provider
768 .span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
769 .unwrap_or("");
770 let is_last = self.inner.peek().is_none();
771 let comment_end =
772 get_comment_end(post_snippet, self.separator, self.terminator, is_last);
773 let new_lines = has_extra_newline(post_snippet, comment_end);
774 let post_comment =
775 extract_post_comment(post_snippet, comment_end, self.separator, is_last);
776
777 self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
778
779 ListItem {
780 pre_comment,
781 pre_comment_style,
782 item: if self.inner.peek().is_none() && self.leave_last {
784 Err(RewriteError::SkipFormatting)
785 } else {
786 (self.get_item_string)(&item)
787 },
788 post_comment,
789 new_lines,
790 }
791 })
792 }
793}
794
795#[allow(clippy::too_many_arguments)]
796pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>(
798 snippet_provider: &'a SnippetProvider,
799 inner: I,
800 terminator: &'a str,
801 separator: &'a str,
802 get_lo: F1,
803 get_hi: F2,
804 get_item_string: F3,
805 prev_span_end: BytePos,
806 next_span_start: BytePos,
807 leave_last: bool,
808) -> ListItems<'a, I, F1, F2, F3>
809where
810 I: Iterator<Item = T>,
811 F1: Fn(&T) -> BytePos,
812 F2: Fn(&T) -> BytePos,
813 F3: Fn(&T) -> RewriteResult,
814{
815 ListItems {
816 snippet_provider,
817 inner: inner.peekable(),
818 get_lo,
819 get_hi,
820 get_item_string,
821 prev_span_end,
822 next_span_start,
823 terminator,
824 separator,
825 leave_last,
826 }
827}
828
829fn calculate_width<I, T>(items: I) -> (usize, usize)
831where
832 I: IntoIterator<Item = T>,
833 T: AsRef<ListItem>,
834{
835 items
836 .into_iter()
837 .map(|item| total_item_width(item.as_ref()))
838 .fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l))
839}
840
841pub(crate) fn total_item_width(item: &ListItem) -> usize {
842 comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..]))
843 + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..]))
844 + item.item.as_ref().map_or(0, |s| unicode_str_width(s))
845}
846
847fn comment_len(comment: Option<&str>) -> usize {
848 match comment {
849 Some(s) => {
850 let text_len = s.trim().len();
851 if text_len > 0 {
852 text_len + 6
854 } else {
855 text_len
856 }
857 }
858 None => 0,
859 }
860}
861
862pub(crate) fn struct_lit_shape(
864 shape: Shape,
865 context: &RewriteContext<'_>,
866 prefix_width: usize,
867 suffix_width: usize,
868) -> Option<(Option<Shape>, Shape)> {
869 let v_shape = match context.config.indent_style() {
870 IndentStyle::Visual => shape
871 .visual_indent(0)
872 .shrink_left(prefix_width)?
873 .sub_width(suffix_width)?,
874 IndentStyle::Block => {
875 let shape = shape.block_indent(context.config.tab_spaces());
876 Shape {
877 width: context.budget(shape.indent.width()),
878 ..shape
879 }
880 }
881 };
882 let shape_width = shape.width.checked_sub(prefix_width + suffix_width);
883 if let Some(w) = shape_width {
884 let shape_width = cmp::min(w, context.config.struct_lit_width());
885 Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
886 } else {
887 Some((None, v_shape))
888 }
889}
890
891pub(crate) fn struct_lit_tactic(
893 h_shape: Option<Shape>,
894 context: &RewriteContext<'_>,
895 items: &[ListItem],
896) -> DefinitiveListTactic {
897 if let Some(h_shape) = h_shape {
898 let prelim_tactic = match (context.config.indent_style(), items.len()) {
899 (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
900 _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
901 _ => ListTactic::Vertical,
902 };
903 definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
904 } else {
905 DefinitiveListTactic::Vertical
906 }
907}
908
909pub(crate) fn shape_for_tactic(
912 tactic: DefinitiveListTactic,
913 h_shape: Option<Shape>,
914 v_shape: Shape,
915) -> Shape {
916 match tactic {
917 DefinitiveListTactic::Horizontal => h_shape.unwrap(),
918 _ => v_shape,
919 }
920}
921
922pub(crate) fn struct_lit_formatting<'a>(
925 shape: Shape,
926 tactic: DefinitiveListTactic,
927 context: &'a RewriteContext<'_>,
928 force_no_trailing_comma: bool,
929) -> ListFormatting<'a> {
930 let ends_with_newline = context.config.indent_style() != IndentStyle::Visual
931 && tactic == DefinitiveListTactic::Vertical;
932 ListFormatting {
933 tactic,
934 separator: ",",
935 trailing_separator: if force_no_trailing_comma {
936 SeparatorTactic::Never
937 } else {
938 context.config.trailing_comma()
939 },
940 separator_place: SeparatorPlace::Back,
941 shape,
942 ends_with_newline,
943 preserve_newline: true,
944 nested: false,
945 align_comments: true,
946 config: context.config,
947 }
948}