Skip to main content

rustfmt_nightly/
shape.rs

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