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