rustfmt_nightly/
shape.rs

1use std::borrow::Cow;
2use std::cmp::min;
3use std::ops::{Add, Sub};
4
5use crate::Config;
6
7#[derive(Copy, Clone, Debug)]
8pub(crate) struct Indent {
9    // Width of the block indent, in characters. Must be a multiple of
10    // Config::tab_spaces.
11    pub(crate) block_indent: usize,
12    // Alignment in characters.
13    pub(crate) alignment: usize,
14}
15
16// INDENT_BUFFER.len() = 81
17const INDENT_BUFFER_LEN: usize = 80;
18const INDENT_BUFFER: &str =
19    "\n                                                                                ";
20
21impl Indent {
22    pub(crate) fn new(block_indent: usize, alignment: usize) -> Indent {
23        Indent {
24            block_indent,
25            alignment,
26        }
27    }
28
29    pub(crate) 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(crate) fn empty() -> Indent {
40        Indent::new(0, 0)
41    }
42
43    pub(crate) fn block_only(&self) -> Indent {
44        Indent {
45            block_indent: self.block_indent,
46            alignment: 0,
47        }
48    }
49
50    pub(crate) fn block_indent(mut self, config: &Config) -> Indent {
51        self.block_indent += config.tab_spaces();
52        self
53    }
54
55    pub(crate) 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(crate) fn width(&self) -> usize {
65        self.block_indent + self.alignment
66    }
67
68    pub(crate) fn to_string(&self, config: &Config) -> Cow<'static, str> {
69        self.to_string_inner(config, 1)
70    }
71
72    pub(crate) 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
101impl 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
112impl 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
123impl 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
131impl 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// 8096 is close enough to infinite for rustfmt.
140const INFINITE_SHAPE_WIDTH: usize = 8096;
141
142#[derive(Copy, Clone, Debug)]
143pub(crate) struct Shape {
144    pub(crate) width: usize,
145    // The current indentation of code.
146    pub(crate) indent: Indent,
147    // Indentation + any already emitted text on the first line of the current
148    // statement.
149    pub(crate) offset: usize,
150}
151
152impl Shape {
153    /// `indent` is the indentation of the first line. The next lines
154    /// should begin with at least `indent` spaces (except backwards
155    /// indentation). The first line should not begin with indentation.
156    /// `width` is the maximum number of characters on the last line
157    /// (excluding `indent`). The width of other lines is not limited by
158    /// `width`.
159    /// Note that in reality, we sometimes use width for lines other than the
160    /// last (i.e., we are conservative).
161    // .......*-------*
162    //        |       |
163    //        |     *-*
164    //        *-----|
165    // |<------------>|  max width
166    // |<---->|          indent
167    //        |<--->|    width
168    pub(crate) fn legacy(width: usize, indent: Indent) -> Shape {
169        Shape {
170            width,
171            indent,
172            offset: indent.alignment,
173        }
174    }
175
176    pub(crate) fn indented(indent: Indent, config: &Config) -> Shape {
177        Shape {
178            width: config.max_width().saturating_sub(indent.width()),
179            indent,
180            offset: indent.alignment,
181        }
182    }
183
184    pub(crate) fn with_max_width(&self, config: &Config) -> Shape {
185        Shape {
186            width: config.max_width().saturating_sub(self.indent.width()),
187            ..*self
188        }
189    }
190
191    pub(crate) fn visual_indent(&self, extra_width: usize) -> Shape {
192        let alignment = self.offset + extra_width;
193        Shape {
194            width: self.width,
195            indent: Indent::new(self.indent.block_indent, alignment),
196            offset: alignment,
197        }
198    }
199
200    pub(crate) fn block_indent(&self, extra_width: usize) -> Shape {
201        if self.indent.alignment == 0 {
202            Shape {
203                width: self.width,
204                indent: Indent::new(self.indent.block_indent + extra_width, 0),
205                offset: 0,
206            }
207        } else {
208            Shape {
209                width: self.width,
210                indent: self.indent + extra_width,
211                offset: self.indent.alignment + extra_width,
212            }
213        }
214    }
215
216    pub(crate) fn block_left(&self, width: usize) -> Option<Shape> {
217        self.block_indent(width).sub_width(width)
218    }
219
220    pub(crate) fn add_offset(&self, extra_width: usize) -> Shape {
221        Shape {
222            offset: self.offset + extra_width,
223            ..*self
224        }
225    }
226
227    pub(crate) fn block(&self) -> Shape {
228        Shape {
229            indent: self.indent.block_only(),
230            ..*self
231        }
232    }
233
234    pub(crate) fn saturating_sub_width(&self, width: usize) -> Shape {
235        self.sub_width(width).unwrap_or(Shape { width: 0, ..*self })
236    }
237
238    pub(crate) fn sub_width(&self, width: usize) -> Option<Shape> {
239        Some(Shape {
240            width: self.width.checked_sub(width)?,
241            ..*self
242        })
243    }
244
245    pub(crate) fn shrink_left(&self, width: usize) -> Option<Shape> {
246        Some(Shape {
247            width: self.width.checked_sub(width)?,
248            indent: self.indent + width,
249            offset: self.offset + width,
250        })
251    }
252
253    pub(crate) fn offset_left(&self, width: usize) -> Option<Shape> {
254        self.add_offset(width).sub_width(width)
255    }
256
257    pub(crate) fn used_width(&self) -> usize {
258        self.indent.block_indent + self.offset
259    }
260
261    pub(crate) fn rhs_overhead(&self, config: &Config) -> usize {
262        config
263            .max_width()
264            .saturating_sub(self.used_width() + self.width)
265    }
266
267    pub(crate) fn comment(&self, config: &Config) -> Shape {
268        let width = min(
269            self.width,
270            config.comment_width().saturating_sub(self.indent.width()),
271        );
272        Shape { width, ..*self }
273    }
274
275    pub(crate) fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> {
276        let mut offset_indent = self.indent;
277        offset_indent.alignment = self.offset;
278        offset_indent.to_string_inner(config, 0)
279    }
280
281    /// Creates a `Shape` with a virtually infinite width.
282    pub(crate) fn infinite_width(&self) -> Shape {
283        Shape {
284            width: INFINITE_SHAPE_WIDTH,
285            ..*self
286        }
287    }
288}
289
290#[cfg(test)]
291mod test {
292    use super::*;
293
294    #[test]
295    fn indent_add_sub() {
296        let indent = Indent::new(4, 8) + Indent::new(8, 12);
297        assert_eq!(12, indent.block_indent);
298        assert_eq!(20, indent.alignment);
299
300        let indent = indent - Indent::new(4, 4);
301        assert_eq!(8, indent.block_indent);
302        assert_eq!(16, indent.alignment);
303    }
304
305    #[test]
306    fn indent_add_sub_alignment() {
307        let indent = Indent::new(4, 8) + 4;
308        assert_eq!(4, indent.block_indent);
309        assert_eq!(12, indent.alignment);
310
311        let indent = indent - 4;
312        assert_eq!(4, indent.block_indent);
313        assert_eq!(8, indent.alignment);
314    }
315
316    #[test]
317    fn indent_to_string_spaces() {
318        let config = Config::default();
319        let indent = Indent::new(4, 8);
320
321        // 12 spaces
322        assert_eq!("            ", indent.to_string(&config));
323    }
324
325    #[test]
326    fn indent_to_string_hard_tabs() {
327        let mut config = Config::default();
328        config.set().hard_tabs(true);
329        let indent = Indent::new(8, 4);
330
331        // 2 tabs + 4 spaces
332        assert_eq!("\t\t    ", indent.to_string(&config));
333    }
334
335    #[test]
336    fn shape_visual_indent() {
337        let config = Config::default();
338        let indent = Indent::new(4, 8);
339        let shape = Shape::legacy(config.max_width(), indent);
340        let shape = shape.visual_indent(20);
341
342        assert_eq!(config.max_width(), shape.width);
343        assert_eq!(4, shape.indent.block_indent);
344        assert_eq!(28, shape.indent.alignment);
345        assert_eq!(28, shape.offset);
346    }
347
348    #[test]
349    fn shape_block_indent_without_alignment() {
350        let config = Config::default();
351        let indent = Indent::new(4, 0);
352        let shape = Shape::legacy(config.max_width(), indent);
353        let shape = shape.block_indent(20);
354
355        assert_eq!(config.max_width(), shape.width);
356        assert_eq!(24, shape.indent.block_indent);
357        assert_eq!(0, shape.indent.alignment);
358        assert_eq!(0, shape.offset);
359    }
360
361    #[test]
362    fn shape_block_indent_with_alignment() {
363        let config = Config::default();
364        let indent = Indent::new(4, 8);
365        let shape = Shape::legacy(config.max_width(), indent);
366        let shape = shape.block_indent(20);
367
368        assert_eq!(config.max_width(), shape.width);
369        assert_eq!(4, shape.indent.block_indent);
370        assert_eq!(28, shape.indent.alignment);
371        assert_eq!(28, shape.offset);
372    }
373}