]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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(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 | |
17 | const INDENT_BUFFER_LEN: usize = 80; | |
18 | const INDENT_BUFFER: &str = | |
19 | "\n "; | |
20 | ||
21 | impl 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 | ||
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 | // 8096 is close enough to infinite for rustfmt. | |
140 | const INFINITE_SHAPE_WIDTH: usize = 8096; | |
141 | ||
142 | #[derive(Copy, Clone, Debug)] | |
143 | pub(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 | ||
152 | impl 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)] | |
291 | mod 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 | } |