]> git.lizzy.rs Git - rust.git/blob - src/lists.rs
Merge pull request #106 from marcusklaas/issue-number-check-2
[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 utils::make_indent;
12
13 #[derive(Eq, PartialEq, Debug, Copy, Clone)]
14 pub enum ListTactic {
15     // One item per row.
16     Vertical,
17     // All items on one row.
18     Horizontal,
19     // Try Horizontal layout, if that fails then vertical
20     HorizontalVertical,
21     // Pack as many items as possible per row over (possibly) many rows.
22     Mixed,
23 }
24
25 #[derive(Eq, PartialEq, Debug, Copy, Clone)]
26 pub enum SeparatorTactic {
27     Always,
28     Never,
29     Vertical,
30 }
31
32 impl_enum_decodable!(SeparatorTactic, Always, Never, Vertical);
33
34 // TODO having some helpful ctors for ListFormatting would be nice.
35 pub struct ListFormatting<'a> {
36     pub tactic: ListTactic,
37     pub separator: &'a str,
38     pub trailing_separator: SeparatorTactic,
39     pub indent: usize,
40     // Available width if we layout horizontally.
41     pub h_width: usize,
42     // Available width if we layout vertically
43     pub v_width: usize,
44 }
45
46 // Format a list of strings into a string.
47 // Precondition: all strings in items are trimmed.
48 pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b>) -> String {
49     if items.len() == 0 {
50         return String::new();
51     }
52
53     let mut tactic = formatting.tactic;
54
55     // Conservatively overestimates because of the changing separator tactic.
56     let sep_count = if formatting.trailing_separator != SeparatorTactic::Never {
57         items.len()
58     } else {
59         items.len() - 1
60     };
61     let sep_len = formatting.separator.len();
62     let total_sep_len = (sep_len + 1) * sep_count;
63     let total_width = calculate_width(items);
64     let fits_single = total_width + total_sep_len <= formatting.h_width;
65
66     // Check if we need to fallback from horizontal listing, if possible.
67     if tactic == ListTactic::HorizontalVertical {
68         debug!("write_list: total_width: {}, total_sep_len: {}, h_width: {}",
69                total_width, total_sep_len, formatting.h_width);
70         tactic = if fits_single &&
71                     !items.iter().any(|&(ref s, _)| s.contains('\n')) {
72             ListTactic::Horizontal
73         } else {
74             ListTactic::Vertical
75         };
76     }
77
78     // Check if we can fit everything on a single line in mixed mode.
79     // The horizontal tactic does not break after v_width columns.
80     if tactic == ListTactic::Mixed && fits_single {
81         tactic = ListTactic::Horizontal;
82     }
83
84     // Now that we know how we will layout, we can decide for sure if there
85     // will be a trailing separator.
86     let trailing_separator = needs_trailing_separator(formatting.trailing_separator, tactic);
87
88     // Create a buffer for the result.
89     // TODO could use a StringBuffer or rope for this
90     let alloc_width = if tactic == ListTactic::Horizontal {
91         total_width + total_sep_len
92     } else {
93         total_width + items.len() * (formatting.indent + 1)
94     };
95     let mut result = String::with_capacity(alloc_width);
96
97     let mut line_len = 0;
98     let indent_str = &make_indent(formatting.indent);
99     for (i, &(ref item, ref comment)) in items.iter().enumerate() {
100         let first = i == 0;
101         let separate = i != items.len() - 1 || trailing_separator;
102
103         match tactic {
104             ListTactic::Horizontal if !first => {
105                 result.push(' ');
106             }
107             ListTactic::Vertical if !first => {
108                 result.push('\n');
109                 result.push_str(indent_str);
110             }
111             ListTactic::Mixed => {
112                 let mut item_width = item.len();
113                 if separate {
114                     item_width += sep_len;
115                 }
116
117                 if line_len > 0 && line_len + item_width > formatting.v_width {
118                     result.push('\n');
119                     result.push_str(indent_str);
120                     line_len = 0;
121                 }
122
123                 if line_len > 0 {
124                     result.push(' ');
125                     line_len += 1;
126                 }
127
128                 line_len += item_width;
129             }
130             _ => {}
131         }
132
133         result.push_str(item);
134
135         if tactic != ListTactic::Vertical && comment.len() > 0 {
136             if !comment.starts_with('\n') {
137                 result.push(' ');
138             }
139             result.push_str(comment);
140         }
141
142         if separate {
143             result.push_str(formatting.separator);
144         }
145
146         if tactic == ListTactic::Vertical && comment.len() > 0 {
147             if !comment.starts_with('\n') {
148                 result.push(' ');
149             }
150             result.push_str(comment);
151         }
152     }
153
154     result
155 }
156
157 fn needs_trailing_separator(separator_tactic: SeparatorTactic, list_tactic: ListTactic) -> bool {
158     match separator_tactic {
159         SeparatorTactic::Always => true,
160         SeparatorTactic::Vertical => list_tactic == ListTactic::Vertical,
161         SeparatorTactic::Never => false,
162     }
163 }
164
165 fn calculate_width(items:&[(String, String)]) -> usize {
166     let missed_width = items.iter().map(|&(_, ref s)| {
167         let text_len = s.trim().len();
168         if text_len > 0 {
169             // We'll put a space before any comment.
170             text_len + 1
171         } else {
172             text_len
173         }
174     }).fold(0, |a, l| a + l);
175     let item_width = items.iter().map(|&(ref s, _)| s.len()).fold(0, |a, l| a + l);
176     missed_width + item_width
177 }