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