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