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