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