]> git.lizzy.rs Git - rust.git/blob - src/lists.rs
rustfmt: add support to specify the Rust edition as argument
[rust.git] / src / lists.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Format list-like expressions and items.
12
13 use std::cmp;
14 use std::iter::Peekable;
15
16 use config::lists::*;
17 use syntax::source_map::BytePos;
18
19 use comment::{find_comment_end, rewrite_comment, FindUncommented};
20 use config::{Config, IndentStyle};
21 use rewrite::RewriteContext;
22 use shape::{Indent, Shape};
23 use utils::{count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline};
24 use visitor::SnippetProvider;
25
26 pub struct ListFormatting<'a> {
27     tactic: DefinitiveListTactic,
28     separator: &'a str,
29     trailing_separator: SeparatorTactic,
30     separator_place: SeparatorPlace,
31     shape: Shape,
32     // Non-expressions, e.g. items, will have a new line at the end of the list.
33     // Important for comment styles.
34     ends_with_newline: bool,
35     // Remove newlines between list elements for expressions.
36     preserve_newline: bool,
37     // Nested import lists get some special handling for the "Mixed" list type
38     nested: bool,
39     config: &'a Config,
40 }
41
42 impl<'a> ListFormatting<'a> {
43     pub fn new(shape: Shape, config: &'a Config) -> Self {
44         ListFormatting {
45             tactic: DefinitiveListTactic::Vertical,
46             separator: ",",
47             trailing_separator: SeparatorTactic::Never,
48             separator_place: SeparatorPlace::Back,
49             shape,
50             ends_with_newline: true,
51             preserve_newline: false,
52             nested: false,
53             config,
54         }
55     }
56
57     pub fn tactic(mut self, tactic: DefinitiveListTactic) -> Self {
58         self.tactic = tactic;
59         self
60     }
61
62     pub fn separator(mut self, separator: &'a str) -> Self {
63         self.separator = separator;
64         self
65     }
66
67     pub fn trailing_separator(mut self, trailing_separator: SeparatorTactic) -> Self {
68         self.trailing_separator = trailing_separator;
69         self
70     }
71
72     pub fn separator_place(mut self, separator_place: SeparatorPlace) -> Self {
73         self.separator_place = separator_place;
74         self
75     }
76
77     pub fn ends_with_newline(mut self, ends_with_newline: bool) -> Self {
78         self.ends_with_newline = ends_with_newline;
79         self
80     }
81
82     pub fn preserve_newline(mut self, preserve_newline: bool) -> Self {
83         self.preserve_newline = preserve_newline;
84         self
85     }
86
87     pub fn nested(mut self, nested: bool) -> Self {
88         self.nested = nested;
89         self
90     }
91
92     pub fn needs_trailing_separator(&self) -> bool {
93         match self.trailing_separator {
94             // We always put separator in front.
95             SeparatorTactic::Always => true,
96             SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical,
97             SeparatorTactic::Never => {
98                 self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front()
99             }
100         }
101     }
102 }
103
104 impl AsRef<ListItem> for ListItem {
105     fn as_ref(&self) -> &ListItem {
106         self
107     }
108 }
109
110 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
111 pub enum ListItemCommentStyle {
112     // Try to keep the comment on the same line with the item.
113     SameLine,
114     // Put the comment on the previous or the next line of the item.
115     DifferentLine,
116     // No comment available.
117     None,
118 }
119
120 #[derive(Debug, Clone)]
121 pub struct ListItem {
122     // None for comments mean that they are not present.
123     pub pre_comment: Option<String>,
124     pub pre_comment_style: ListItemCommentStyle,
125     // Item should include attributes and doc comments. None indicates a failed
126     // rewrite.
127     pub item: Option<String>,
128     pub post_comment: Option<String>,
129     // Whether there is extra whitespace before this item.
130     pub new_lines: bool,
131 }
132
133 impl ListItem {
134     pub fn empty() -> ListItem {
135         ListItem {
136             pre_comment: None,
137             pre_comment_style: ListItemCommentStyle::None,
138             item: None,
139             post_comment: None,
140             new_lines: false,
141         }
142     }
143
144     pub fn inner_as_ref(&self) -> &str {
145         self.item.as_ref().map_or("", |s| s)
146     }
147
148     pub fn is_different_group(&self) -> bool {
149         self.inner_as_ref().contains('\n')
150             || self.pre_comment.is_some()
151             || self
152                 .post_comment
153                 .as_ref()
154                 .map_or(false, |s| s.contains('\n'))
155     }
156
157     pub fn is_multiline(&self) -> bool {
158         self.inner_as_ref().contains('\n')
159             || self
160                 .pre_comment
161                 .as_ref()
162                 .map_or(false, |s| s.contains('\n'))
163             || self
164                 .post_comment
165                 .as_ref()
166                 .map_or(false, |s| s.contains('\n'))
167     }
168
169     pub fn has_single_line_comment(&self) -> bool {
170         self.pre_comment
171             .as_ref()
172             .map_or(false, |comment| comment.trim_left().starts_with("//"))
173             || self
174                 .post_comment
175                 .as_ref()
176                 .map_or(false, |comment| comment.trim_left().starts_with("//"))
177     }
178
179     pub fn has_comment(&self) -> bool {
180         self.pre_comment.is_some() || self.post_comment.is_some()
181     }
182
183     pub fn from_str<S: Into<String>>(s: S) -> ListItem {
184         ListItem {
185             pre_comment: None,
186             pre_comment_style: ListItemCommentStyle::None,
187             item: Some(s.into()),
188             post_comment: None,
189             new_lines: false,
190         }
191     }
192
193     // true if the item causes something to be written.
194     fn is_substantial(&self) -> bool {
195         fn empty(s: &Option<String>) -> bool {
196             match *s {
197                 Some(ref s) if !s.is_empty() => false,
198                 _ => true,
199             }
200         }
201
202         !(empty(&self.pre_comment) && empty(&self.item) && empty(&self.post_comment))
203     }
204 }
205
206 /// The type of separator for lists.
207 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
208 pub enum Separator {
209     Comma,
210     VerticalBar,
211 }
212
213 impl Separator {
214     pub fn len(self) -> usize {
215         match self {
216             // 2 = `, `
217             Separator::Comma => 2,
218             // 3 = ` | `
219             Separator::VerticalBar => 3,
220         }
221     }
222 }
223
224 pub fn definitive_tactic<I, T>(
225     items: I,
226     tactic: ListTactic,
227     sep: Separator,
228     width: usize,
229 ) -> DefinitiveListTactic
230 where
231     I: IntoIterator<Item = T> + Clone,
232     T: AsRef<ListItem>,
233 {
234     let pre_line_comments = items
235         .clone()
236         .into_iter()
237         .any(|item| item.as_ref().has_single_line_comment());
238
239     let limit = match tactic {
240         _ if pre_line_comments => return DefinitiveListTactic::Vertical,
241         ListTactic::Horizontal => return DefinitiveListTactic::Horizontal,
242         ListTactic::Vertical => return DefinitiveListTactic::Vertical,
243         ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit),
244         ListTactic::Mixed | ListTactic::HorizontalVertical => width,
245     };
246
247     let (sep_count, total_width) = calculate_width(items.clone());
248     let total_sep_len = sep.len() * sep_count.saturating_sub(1);
249     let real_total = total_width + total_sep_len;
250
251     if real_total <= limit
252         && !pre_line_comments
253         && !items.into_iter().any(|item| item.as_ref().is_multiline())
254     {
255         DefinitiveListTactic::Horizontal
256     } else {
257         match tactic {
258             ListTactic::Mixed => DefinitiveListTactic::Mixed,
259             _ => DefinitiveListTactic::Vertical,
260         }
261     }
262 }
263
264 // Format a list of commented items into a string.
265 pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String>
266 where
267     I: IntoIterator<Item = T> + Clone,
268     T: AsRef<ListItem>,
269 {
270     let tactic = formatting.tactic;
271     let sep_len = formatting.separator.len();
272
273     // Now that we know how we will layout, we can decide for sure if there
274     // will be a trailing separator.
275     let mut trailing_separator = formatting.needs_trailing_separator();
276     let mut result = String::with_capacity(128);
277     let cloned_items = items.clone();
278     let mut iter = items.into_iter().enumerate().peekable();
279     let mut item_max_width: Option<usize> = None;
280     let sep_place =
281         SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator);
282     let mut prev_item_had_post_comment = false;
283     let mut prev_item_is_nested_import = false;
284
285     let mut line_len = 0;
286     let indent_str = &formatting.shape.indent.to_string(formatting.config);
287     while let Some((i, item)) = iter.next() {
288         let item = item.as_ref();
289         let inner_item = item.item.as_ref()?;
290         let first = i == 0;
291         let last = iter.peek().is_none();
292         let mut separate = match sep_place {
293             SeparatorPlace::Front => !first,
294             SeparatorPlace::Back => !last || trailing_separator,
295         };
296         let item_sep_len = if separate { sep_len } else { 0 };
297
298         // Item string may be multi-line. Its length (used for block comment alignment)
299         // should be only the length of the last line.
300         let item_last_line = if item.is_multiline() {
301             inner_item.lines().last().unwrap_or("")
302         } else {
303             inner_item.as_ref()
304         };
305         let mut item_last_line_width = item_last_line.len() + item_sep_len;
306         if item_last_line.starts_with(&**indent_str) {
307             item_last_line_width -= indent_str.len();
308         }
309
310         if !item.is_substantial() {
311             continue;
312         }
313
314         match tactic {
315             DefinitiveListTactic::Horizontal if !first => {
316                 result.push(' ');
317             }
318             DefinitiveListTactic::SpecialMacro(num_args_before) => {
319                 if i == 0 {
320                     // Nothing
321                 } else if i < num_args_before {
322                     result.push(' ');
323                 } else if i <= num_args_before + 1 {
324                     result.push('\n');
325                     result.push_str(indent_str);
326                 } else {
327                     result.push(' ');
328                 }
329             }
330             DefinitiveListTactic::Vertical
331                 if !first && !inner_item.is_empty() && !result.is_empty() =>
332             {
333                 result.push('\n');
334                 result.push_str(indent_str);
335             }
336             DefinitiveListTactic::Mixed => {
337                 let total_width = total_item_width(item) + item_sep_len;
338
339                 // 1 is space between separator and item.
340                 if (line_len > 0 && line_len + 1 + total_width > formatting.shape.width)
341                     || prev_item_had_post_comment
342                     || (formatting.nested
343                         && (prev_item_is_nested_import || (!first && inner_item.contains("::"))))
344                 {
345                     result.push('\n');
346                     result.push_str(indent_str);
347                     line_len = 0;
348                     if formatting.ends_with_newline {
349                         trailing_separator = true;
350                     }
351                 } else if line_len > 0 {
352                     result.push(' ');
353                     line_len += 1;
354                 }
355
356                 if last && formatting.ends_with_newline {
357                     separate = formatting.trailing_separator != SeparatorTactic::Never;
358                 }
359
360                 line_len += total_width;
361             }
362             _ => {}
363         }
364
365         // Pre-comments
366         if let Some(ref comment) = item.pre_comment {
367             // Block style in non-vertical mode.
368             let block_mode = tactic == DefinitiveListTactic::Horizontal;
369             // Width restriction is only relevant in vertical mode.
370             let comment =
371                 rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?;
372             result.push_str(&comment);
373
374             if !inner_item.is_empty() {
375                 if tactic == DefinitiveListTactic::Vertical || tactic == DefinitiveListTactic::Mixed
376                 {
377                     // We cannot keep pre-comments on the same line if the comment if normalized.
378                     let keep_comment = if formatting.config.normalize_comments()
379                         || item.pre_comment_style == ListItemCommentStyle::DifferentLine
380                     {
381                         false
382                     } else {
383                         // We will try to keep the comment on the same line with the item here.
384                         // 1 = ` `
385                         let total_width = total_item_width(item) + item_sep_len + 1;
386                         total_width <= formatting.shape.width
387                     };
388                     if keep_comment {
389                         result.push(' ');
390                     } else {
391                         result.push('\n');
392                         result.push_str(indent_str);
393                         // This is the width of the item (without comments).
394                         line_len = item.item.as_ref().map_or(0, |str| str.len());
395                     }
396                 } else {
397                     result.push(' ');
398                 }
399             }
400             item_max_width = None;
401         }
402
403         if separate && sep_place.is_front() && !first {
404             result.push_str(formatting.separator.trim());
405             result.push(' ');
406         }
407         result.push_str(inner_item);
408
409         // Post-comments
410         if tactic == DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
411             let comment = item.post_comment.as_ref().unwrap();
412             let formatted_comment = rewrite_comment(
413                 comment,
414                 true,
415                 Shape::legacy(formatting.shape.width, Indent::empty()),
416                 formatting.config,
417             )?;
418
419             result.push(' ');
420             result.push_str(&formatted_comment);
421         }
422
423         if separate && sep_place.is_back() {
424             result.push_str(formatting.separator);
425         }
426
427         if tactic != DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
428             let comment = item.post_comment.as_ref().unwrap();
429             let overhead = last_line_width(&result) + 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                     // 1 = space between item and comment.
446                     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                 // Use block-style only for the last item or multiline comments.
453                 let block_style = !formatting.ends_with_newline && last
454                     || comment.trim().contains('\n')
455                     || comment.trim().len() > width;
456
457                 rewrite_comment(
458                     comment.trim_left(),
459                     block_style,
460                     comment_shape,
461                     formatting.config,
462                 )
463             };
464
465             let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
466
467             if !starts_with_newline(comment) {
468                 let mut comment_alignment =
469                     post_comment_alignment(item_max_width, inner_item.len());
470                 if first_line_width(&formatted_comment)
471                     + last_line_width(&result)
472                     + comment_alignment
473                     + 1
474                     > formatting.config.max_width()
475                 {
476                     item_max_width = None;
477                     formatted_comment = rewrite_post_comment(&mut item_max_width)?;
478                     comment_alignment = post_comment_alignment(item_max_width, inner_item.len());
479                 }
480                 for _ in 0..=comment_alignment {
481                     result.push(' ');
482                 }
483                 // An additional space for the missing trailing separator.
484                 if last && item_max_width.is_some() && !separate && !formatting.separator.is_empty()
485                 {
486                     result.push(' ');
487                 }
488             } else {
489                 result.push('\n');
490                 result.push_str(indent_str);
491             }
492             if formatted_comment.contains('\n') {
493                 item_max_width = None;
494             }
495             result.push_str(&formatted_comment);
496         } else {
497             item_max_width = None;
498         }
499
500         if formatting.preserve_newline
501             && !last
502             && tactic == DefinitiveListTactic::Vertical
503             && item.new_lines
504         {
505             item_max_width = None;
506             result.push('\n');
507         }
508
509         prev_item_had_post_comment = item.post_comment.is_some();
510         prev_item_is_nested_import = inner_item.contains("::");
511     }
512
513     Some(result)
514 }
515
516 fn max_width_of_item_with_post_comment<I, T>(
517     items: &I,
518     i: usize,
519     overhead: usize,
520     max_budget: usize,
521 ) -> usize
522 where
523     I: IntoIterator<Item = T> + Clone,
524     T: AsRef<ListItem>,
525 {
526     let mut max_width = 0;
527     let mut first = true;
528     for item in items.clone().into_iter().skip(i) {
529         let item = item.as_ref();
530         let inner_item_width = item.inner_as_ref().len();
531         if !first
532             && (item.is_different_group()
533                 || item.post_comment.is_none()
534                 || inner_item_width + overhead > max_budget)
535         {
536             return max_width;
537         }
538         if max_width < inner_item_width {
539             max_width = inner_item_width;
540         }
541         if item.new_lines {
542             return max_width;
543         }
544         first = false;
545     }
546     max_width
547 }
548
549 fn post_comment_alignment(item_max_width: Option<usize>, inner_item_len: usize) -> usize {
550     item_max_width.unwrap_or(0).saturating_sub(inner_item_len)
551 }
552
553 pub struct ListItems<'a, I, F1, F2, F3>
554 where
555     I: Iterator,
556 {
557     snippet_provider: &'a SnippetProvider<'a>,
558     inner: Peekable<I>,
559     get_lo: F1,
560     get_hi: F2,
561     get_item_string: F3,
562     prev_span_end: BytePos,
563     next_span_start: BytePos,
564     terminator: &'a str,
565     separator: &'a str,
566     leave_last: bool,
567 }
568
569 pub fn extract_pre_comment(pre_snippet: &str) -> (Option<String>, ListItemCommentStyle) {
570     let trimmed_pre_snippet = pre_snippet.trim();
571     let has_block_comment = trimmed_pre_snippet.ends_with("*/");
572     let has_single_line_comment = trimmed_pre_snippet.starts_with("//");
573     if has_block_comment {
574         let comment_end = pre_snippet.chars().rev().position(|c| c == '/').unwrap();
575         if pre_snippet
576             .chars()
577             .rev()
578             .take(comment_end + 1)
579             .any(|c| c == '\n')
580         {
581             (
582                 Some(trimmed_pre_snippet.to_owned()),
583                 ListItemCommentStyle::DifferentLine,
584             )
585         } else {
586             (
587                 Some(trimmed_pre_snippet.to_owned()),
588                 ListItemCommentStyle::SameLine,
589             )
590         }
591     } else if has_single_line_comment {
592         (
593             Some(trimmed_pre_snippet.to_owned()),
594             ListItemCommentStyle::DifferentLine,
595         )
596     } else {
597         (None, ListItemCommentStyle::None)
598     }
599 }
600
601 pub fn extract_post_comment(
602     post_snippet: &str,
603     comment_end: usize,
604     separator: &str,
605 ) -> Option<String> {
606     let white_space: &[_] = &[' ', '\t'];
607
608     // Cleanup post-comment: strip separators and whitespace.
609     let post_snippet = post_snippet[..comment_end].trim();
610     let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
611         post_snippet[1..].trim_matches(white_space)
612     } else if post_snippet.starts_with(separator) {
613         post_snippet[separator.len()..].trim_matches(white_space)
614     } else if post_snippet.ends_with(',') {
615         post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
616     } else {
617         post_snippet
618     };
619
620     if !post_snippet_trimmed.is_empty() {
621         Some(post_snippet_trimmed.to_owned())
622     } else {
623         None
624     }
625 }
626
627 pub fn get_comment_end(
628     post_snippet: &str,
629     separator: &str,
630     terminator: &str,
631     is_last: bool,
632 ) -> usize {
633     if is_last {
634         return post_snippet
635             .find_uncommented(terminator)
636             .unwrap_or_else(|| post_snippet.len());
637     }
638
639     let mut block_open_index = post_snippet.find("/*");
640     // check if it really is a block comment (and not `//*` or a nested comment)
641     if let Some(i) = block_open_index {
642         match post_snippet.find('/') {
643             Some(j) if j < i => block_open_index = None,
644             _ if i > 0 && &post_snippet[i - 1..i] == "/" => block_open_index = None,
645             _ => (),
646         }
647     }
648     let newline_index = post_snippet.find('\n');
649     if let Some(separator_index) = post_snippet.find_uncommented(separator) {
650         match (block_open_index, newline_index) {
651             // Separator before comment, with the next item on same line.
652             // Comment belongs to next item.
653             (Some(i), None) if i > separator_index => separator_index + 1,
654             // Block-style post-comment before the separator.
655             (Some(i), None) => cmp::max(
656                 find_comment_end(&post_snippet[i..]).unwrap() + i,
657                 separator_index + 1,
658             ),
659             // Block-style post-comment. Either before or after the separator.
660             (Some(i), Some(j)) if i < j => cmp::max(
661                 find_comment_end(&post_snippet[i..]).unwrap() + i,
662                 separator_index + 1,
663             ),
664             // Potential *single* line comment.
665             (_, Some(j)) if j > separator_index => j + 1,
666             _ => post_snippet.len(),
667         }
668     } else if let Some(newline_index) = newline_index {
669         // Match arms may not have trailing comma. In any case, for match arms,
670         // we will assume that the post comment belongs to the next arm if they
671         // do not end with trailing comma.
672         newline_index + 1
673     } else {
674         0
675     }
676 }
677
678 // Account for extra whitespace between items. This is fiddly
679 // because of the way we divide pre- and post- comments.
680 fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool {
681     if post_snippet.is_empty() || comment_end == 0 {
682         return false;
683     }
684
685     // Everything from the separator to the next item.
686     let test_snippet = &post_snippet[comment_end - 1..];
687     let first_newline = test_snippet
688         .find('\n')
689         .unwrap_or_else(|| test_snippet.len());
690     // From the end of the first line of comments.
691     let test_snippet = &test_snippet[first_newline..];
692     let first = test_snippet
693         .find(|c: char| !c.is_whitespace())
694         .unwrap_or_else(|| test_snippet.len());
695     // From the end of the first line of comments to the next non-whitespace char.
696     let test_snippet = &test_snippet[..first];
697
698     // There were multiple line breaks which got trimmed to nothing.
699     count_newlines(test_snippet) > 1
700 }
701
702 impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
703 where
704     I: Iterator<Item = T>,
705     F1: Fn(&T) -> BytePos,
706     F2: Fn(&T) -> BytePos,
707     F3: Fn(&T) -> Option<String>,
708 {
709     type Item = ListItem;
710
711     fn next(&mut self) -> Option<Self::Item> {
712         self.inner.next().map(|item| {
713             // Pre-comment
714             let pre_snippet = self
715                 .snippet_provider
716                 .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
717                 .unwrap_or("");
718             let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet);
719
720             // Post-comment
721             let next_start = match self.inner.peek() {
722                 Some(next_item) => (self.get_lo)(next_item),
723                 None => self.next_span_start,
724             };
725             let post_snippet = self
726                 .snippet_provider
727                 .span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
728                 .unwrap_or("");
729             let comment_end = get_comment_end(
730                 post_snippet,
731                 self.separator,
732                 self.terminator,
733                 self.inner.peek().is_none(),
734             );
735             let new_lines = has_extra_newline(post_snippet, comment_end);
736             let post_comment = extract_post_comment(post_snippet, comment_end, self.separator);
737
738             self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
739
740             ListItem {
741                 pre_comment,
742                 pre_comment_style,
743                 item: if self.inner.peek().is_none() && self.leave_last {
744                     None
745                 } else {
746                     (self.get_item_string)(&item)
747                 },
748                 post_comment,
749                 new_lines,
750             }
751         })
752     }
753 }
754
755 #[allow(clippy::too_many_arguments)]
756 // Creates an iterator over a list's items with associated comments.
757 pub fn itemize_list<'a, T, I, F1, F2, F3>(
758     snippet_provider: &'a SnippetProvider,
759     inner: I,
760     terminator: &'a str,
761     separator: &'a str,
762     get_lo: F1,
763     get_hi: F2,
764     get_item_string: F3,
765     prev_span_end: BytePos,
766     next_span_start: BytePos,
767     leave_last: bool,
768 ) -> ListItems<'a, I, F1, F2, F3>
769 where
770     I: Iterator<Item = T>,
771     F1: Fn(&T) -> BytePos,
772     F2: Fn(&T) -> BytePos,
773     F3: Fn(&T) -> Option<String>,
774 {
775     ListItems {
776         snippet_provider,
777         inner: inner.peekable(),
778         get_lo,
779         get_hi,
780         get_item_string,
781         prev_span_end,
782         next_span_start,
783         terminator,
784         separator,
785         leave_last,
786     }
787 }
788
789 /// Returns the count and total width of the list items.
790 fn calculate_width<I, T>(items: I) -> (usize, usize)
791 where
792     I: IntoIterator<Item = T>,
793     T: AsRef<ListItem>,
794 {
795     items
796         .into_iter()
797         .map(|item| total_item_width(item.as_ref()))
798         .fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l))
799 }
800
801 pub fn total_item_width(item: &ListItem) -> usize {
802     comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..]))
803         + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..]))
804         + item.item.as_ref().map_or(0, |str| str.len())
805 }
806
807 fn comment_len(comment: Option<&str>) -> usize {
808     match comment {
809         Some(s) => {
810             let text_len = s.trim().len();
811             if text_len > 0 {
812                 // We'll put " /*" before and " */" after inline comments.
813                 text_len + 6
814             } else {
815                 text_len
816             }
817         }
818         None => 0,
819     }
820 }
821
822 // Compute horizontal and vertical shapes for a struct-lit-like thing.
823 pub fn struct_lit_shape(
824     shape: Shape,
825     context: &RewriteContext,
826     prefix_width: usize,
827     suffix_width: usize,
828 ) -> Option<(Option<Shape>, Shape)> {
829     let v_shape = match context.config.indent_style() {
830         IndentStyle::Visual => shape
831             .visual_indent(0)
832             .shrink_left(prefix_width)?
833             .sub_width(suffix_width)?,
834         IndentStyle::Block => {
835             let shape = shape.block_indent(context.config.tab_spaces());
836             Shape {
837                 width: context.budget(shape.indent.width()),
838                 ..shape
839             }
840         }
841     };
842     let shape_width = shape.width.checked_sub(prefix_width + suffix_width);
843     if let Some(w) = shape_width {
844         let shape_width = cmp::min(w, context.config.width_heuristics().struct_lit_width);
845         Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
846     } else {
847         Some((None, v_shape))
848     }
849 }
850
851 // Compute the tactic for the internals of a struct-lit-like thing.
852 pub fn struct_lit_tactic(
853     h_shape: Option<Shape>,
854     context: &RewriteContext,
855     items: &[ListItem],
856 ) -> DefinitiveListTactic {
857     if let Some(h_shape) = h_shape {
858         let prelim_tactic = match (context.config.indent_style(), items.len()) {
859             (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
860             _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
861             _ => ListTactic::Vertical,
862         };
863         definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
864     } else {
865         DefinitiveListTactic::Vertical
866     }
867 }
868
869 // Given a tactic and possible shapes for horizontal and vertical layout,
870 // come up with the actual shape to use.
871 pub fn shape_for_tactic(
872     tactic: DefinitiveListTactic,
873     h_shape: Option<Shape>,
874     v_shape: Shape,
875 ) -> Shape {
876     match tactic {
877         DefinitiveListTactic::Horizontal => h_shape.unwrap(),
878         _ => v_shape,
879     }
880 }
881
882 // Create a ListFormatting object for formatting the internals of a
883 // struct-lit-like thing, that is a series of fields.
884 pub fn struct_lit_formatting<'a>(
885     shape: Shape,
886     tactic: DefinitiveListTactic,
887     context: &'a RewriteContext,
888     force_no_trailing_comma: bool,
889 ) -> ListFormatting<'a> {
890     let ends_with_newline = context.config.indent_style() != IndentStyle::Visual
891         && tactic == DefinitiveListTactic::Vertical;
892     ListFormatting {
893         tactic,
894         separator: ",",
895         trailing_separator: if force_no_trailing_comma {
896             SeparatorTactic::Never
897         } else {
898             context.config.trailing_comma()
899         },
900         separator_place: SeparatorPlace::Back,
901         shape,
902         ends_with_newline,
903         preserve_newline: true,
904         nested: false,
905         config: context.config,
906     }
907 }