]> git.lizzy.rs Git - rust.git/blob - src/shape.rs
Change return type of Indent::to_string() to Cow<'static, str>
[rust.git] / src / shape.rs
1 // Copyright 2017 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::borrow::Cow;
12 use std::ops::{Add, Sub};
13
14 use Config;
15
16 #[derive(Copy, Clone, Debug)]
17 pub struct Indent {
18     // Width of the block indent, in characters. Must be a multiple of
19     // Config::tab_spaces.
20     pub block_indent: usize,
21     // Alignment in characters.
22     pub alignment: usize,
23 }
24
25 // INDENT_BUFFER.len() = 80
26 const INDENT_BUFFER_LEN: usize = 80;
27 const INDENT_BUFFER: &str =
28     "                                                                                ";
29 impl Indent {
30     pub fn new(block_indent: usize, alignment: usize) -> Indent {
31         Indent {
32             block_indent: block_indent,
33             alignment: alignment,
34         }
35     }
36
37     pub fn from_width(config: &Config, width: usize) -> Indent {
38         if config.hard_tabs() {
39             let tab_num = width / config.tab_spaces();
40             let alignment = width % config.tab_spaces();
41             Indent::new(config.tab_spaces() * tab_num, alignment)
42         } else {
43             Indent::new(width, 0)
44         }
45     }
46
47     pub fn empty() -> Indent {
48         Indent::new(0, 0)
49     }
50
51     pub fn block_only(&self) -> Indent {
52         Indent {
53             block_indent: self.block_indent,
54             alignment: 0,
55         }
56     }
57
58     pub fn block_indent(mut self, config: &Config) -> Indent {
59         self.block_indent += config.tab_spaces();
60         self
61     }
62
63     pub fn block_unindent(mut self, config: &Config) -> Indent {
64         if self.block_indent < config.tab_spaces() {
65             Indent::new(self.block_indent, 0)
66         } else {
67             self.block_indent -= config.tab_spaces();
68             self
69         }
70     }
71
72     pub fn width(&self) -> usize {
73         self.block_indent + self.alignment
74     }
75
76     pub fn to_string(&self, config: &Config) -> Cow<'static, str> {
77         let (num_tabs, num_spaces) = if config.hard_tabs() {
78             (self.block_indent / config.tab_spaces(), self.alignment)
79         } else {
80             (0, self.width())
81         };
82         let num_chars = num_tabs + num_spaces;
83         if num_tabs == 0 && num_chars <= INDENT_BUFFER_LEN {
84             Cow::from(&INDENT_BUFFER[0..num_chars])
85         } else {
86             let mut indent = String::with_capacity(num_chars);
87             for _ in 0..num_tabs {
88                 indent.push('\t')
89             }
90             for _ in 0..num_spaces {
91                 indent.push(' ')
92             }
93             Cow::from(indent)
94         }
95     }
96 }
97
98 impl Add for Indent {
99     type Output = Indent;
100
101     fn add(self, rhs: Indent) -> Indent {
102         Indent {
103             block_indent: self.block_indent + rhs.block_indent,
104             alignment: self.alignment + rhs.alignment,
105         }
106     }
107 }
108
109 impl Sub for Indent {
110     type Output = Indent;
111
112     fn sub(self, rhs: Indent) -> Indent {
113         Indent::new(
114             self.block_indent - rhs.block_indent,
115             self.alignment - rhs.alignment,
116         )
117     }
118 }
119
120 impl Add<usize> for Indent {
121     type Output = Indent;
122
123     fn add(self, rhs: usize) -> Indent {
124         Indent::new(self.block_indent, self.alignment + rhs)
125     }
126 }
127
128 impl Sub<usize> for Indent {
129     type Output = Indent;
130
131     fn sub(self, rhs: usize) -> Indent {
132         Indent::new(self.block_indent, self.alignment - rhs)
133     }
134 }
135
136 #[derive(Copy, Clone, Debug)]
137 pub struct Shape {
138     pub width: usize,
139     // The current indentation of code.
140     pub indent: Indent,
141     // Indentation + any already emitted text on the first line of the current
142     // statement.
143     pub offset: usize,
144 }
145
146 impl Shape {
147     /// `indent` is the indentation of the first line. The next lines
148     /// should begin with at least `indent` spaces (except backwards
149     /// indentation). The first line should not begin with indentation.
150     /// `width` is the maximum number of characters on the last line
151     /// (excluding `indent`). The width of other lines is not limited by
152     /// `width`.
153     /// Note that in reality, we sometimes use width for lines other than the
154     /// last (i.e., we are conservative).
155     // .......*-------*
156     //        |       |
157     //        |     *-*
158     //        *-----|
159     // |<------------>|  max width
160     // |<---->|          indent
161     //        |<--->|    width
162     pub fn legacy(width: usize, indent: Indent) -> Shape {
163         Shape {
164             width: width,
165             indent: indent,
166             offset: indent.alignment,
167         }
168     }
169
170     pub fn indented(indent: Indent, config: &Config) -> Shape {
171         Shape {
172             width: config.max_width().checked_sub(indent.width()).unwrap_or(0),
173             indent: indent,
174             offset: indent.alignment,
175         }
176     }
177
178     pub fn with_max_width(&self, config: &Config) -> Shape {
179         Shape {
180             width: config
181                 .max_width()
182                 .checked_sub(self.indent.width())
183                 .unwrap_or(0),
184             ..*self
185         }
186     }
187
188     pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape {
189         Shape {
190             width: width,
191             indent: indent,
192             offset: offset,
193         }
194     }
195
196     pub fn visual_indent(&self, extra_width: usize) -> Shape {
197         let alignment = self.offset + extra_width;
198         Shape {
199             width: self.width,
200             indent: Indent::new(self.indent.block_indent, alignment),
201             offset: alignment,
202         }
203     }
204
205     pub fn block_indent(&self, extra_width: usize) -> Shape {
206         if self.indent.alignment == 0 {
207             Shape {
208                 width: self.width,
209                 indent: Indent::new(self.indent.block_indent + extra_width, 0),
210                 offset: 0,
211             }
212         } else {
213             Shape {
214                 width: self.width,
215                 indent: self.indent + extra_width,
216                 offset: self.indent.alignment + extra_width,
217             }
218         }
219     }
220
221     pub fn block_left(&self, width: usize) -> Option<Shape> {
222         self.block_indent(width).sub_width(width)
223     }
224
225     pub fn add_offset(&self, extra_width: usize) -> Shape {
226         Shape {
227             offset: self.offset + extra_width,
228             ..*self
229         }
230     }
231
232     pub fn block(&self) -> Shape {
233         Shape {
234             indent: self.indent.block_only(),
235             ..*self
236         }
237     }
238
239     pub fn sub_width(&self, width: usize) -> Option<Shape> {
240         Some(Shape {
241             width: self.width.checked_sub(width)?,
242             ..*self
243         })
244     }
245
246     pub fn shrink_left(&self, width: usize) -> Option<Shape> {
247         Some(Shape {
248             width: self.width.checked_sub(width)?,
249             indent: self.indent + width,
250             offset: self.offset + width,
251         })
252     }
253
254     pub fn offset_left(&self, width: usize) -> Option<Shape> {
255         self.add_offset(width).sub_width(width)
256     }
257
258     pub fn used_width(&self) -> usize {
259         self.indent.block_indent + self.offset
260     }
261
262     pub fn rhs_overhead(&self, config: &Config) -> usize {
263         config
264             .max_width()
265             .checked_sub(self.used_width() + self.width)
266             .unwrap_or(0)
267     }
268 }
269
270 #[cfg(test)]
271 mod test {
272     use super::*;
273
274     #[test]
275     fn indent_add_sub() {
276         let indent = Indent::new(4, 8) + Indent::new(8, 12);
277         assert_eq!(12, indent.block_indent);
278         assert_eq!(20, indent.alignment);
279
280         let indent = indent - Indent::new(4, 4);
281         assert_eq!(8, indent.block_indent);
282         assert_eq!(16, indent.alignment);
283     }
284
285     #[test]
286     fn indent_add_sub_alignment() {
287         let indent = Indent::new(4, 8) + 4;
288         assert_eq!(4, indent.block_indent);
289         assert_eq!(12, indent.alignment);
290
291         let indent = indent - 4;
292         assert_eq!(4, indent.block_indent);
293         assert_eq!(8, indent.alignment);
294     }
295
296     #[test]
297     fn indent_to_string_spaces() {
298         let config = Config::default();
299         let indent = Indent::new(4, 8);
300
301         // 12 spaces
302         assert_eq!("            ", indent.to_string(&config));
303     }
304
305     #[test]
306     fn indent_to_string_hard_tabs() {
307         let mut config = Config::default();
308         config.set().hard_tabs(true);
309         let indent = Indent::new(8, 4);
310
311         // 2 tabs + 4 spaces
312         assert_eq!("\t\t    ", indent.to_string(&config));
313     }
314
315     #[test]
316     fn shape_visual_indent() {
317         let config = Config::default();
318         let indent = Indent::new(4, 8);
319         let shape = Shape::legacy(config.max_width(), indent);
320         let shape = shape.visual_indent(20);
321
322         assert_eq!(config.max_width(), shape.width);
323         assert_eq!(4, shape.indent.block_indent);
324         assert_eq!(28, shape.indent.alignment);
325         assert_eq!(28, shape.offset);
326     }
327
328     #[test]
329     fn shape_block_indent_without_alignment() {
330         let config = Config::default();
331         let indent = Indent::new(4, 0);
332         let shape = Shape::legacy(config.max_width(), indent);
333         let shape = shape.block_indent(20);
334
335         assert_eq!(config.max_width(), shape.width);
336         assert_eq!(24, shape.indent.block_indent);
337         assert_eq!(0, shape.indent.alignment);
338         assert_eq!(0, shape.offset);
339     }
340
341     #[test]
342     fn shape_block_indent_with_alignment() {
343         let config = Config::default();
344         let indent = Indent::new(4, 8);
345         let shape = Shape::legacy(config.max_width(), indent);
346         let shape = shape.block_indent(20);
347
348         assert_eq!(config.max_width(), shape.width);
349         assert_eq!(4, shape.indent.block_indent);
350         assert_eq!(28, shape.indent.alignment);
351         assert_eq!(28, shape.offset);
352     }
353 }