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