1use {
5 crate::{Format, FormatOutput, FormatTag, Formattable, WhitespaceConfig},
6 core::ops::ControlFlow,
7 itertools::Itertools,
8 rustidy_util::{AstStr, ast_str::AstStrRepr, whitespace::{Comment, Whitespace}},
9 std::sync::Arc,
10};
11
12#[extend::ext(name = WhitespaceFormat)]
13pub impl Whitespace {
14 const INDENT: WhitespaceConfig = WhitespaceConfig { format: Some(
15 WhitespaceFormatKind::Indent { use_prev: false, remove_if_pure: false, }
16 ), };
17 const PRESERVE: WhitespaceConfig = WhitespaceConfig { format: None };
18 const INDENT_CLOSE: WhitespaceConfig = WhitespaceConfig { format: Some(
19 WhitespaceFormatKind::Indent { use_prev: true, remove_if_pure: false, }
20 ), };
21 const INDENT_CLOSE_REMOVE_IF_PURE: WhitespaceConfig = WhitespaceConfig { format: Some(
22 WhitespaceFormatKind::Indent { use_prev: true, remove_if_pure: true, }
23 ), };
24 const INDENT_REMOVE_IF_PURE: WhitespaceConfig = WhitespaceConfig { format: Some(
25 WhitespaceFormatKind::Indent { use_prev: false, remove_if_pure: true, }
26 ), };
27 const REMOVE: WhitespaceConfig = WhitespaceConfig { format: Some(WhitespaceFormatKind::Remove), };
28 const SINGLE: WhitespaceConfig = WhitespaceConfig {
29 format: Some(WhitespaceFormatKind::Spaces { len: 1 }),
30 };
31
32 fn spaces(len: usize) -> WhitespaceConfig {
33 let len = u16::try_from(len)
34 .expect("Cannot format more than 2^16 spaces");
35 WhitespaceConfig {
36 format: Some(WhitespaceFormatKind::Spaces { len }),
37 }
38 }
39
40 fn indent(remove_if_pure: bool) -> WhitespaceConfig {
41 WhitespaceConfig { format: Some(
42 WhitespaceFormatKind::Indent { use_prev: false, remove_if_pure }
43 ), }
44 }
45
46 fn prev_indent(remove_if_pure: bool) -> WhitespaceConfig {
47 WhitespaceConfig { format: Some(
48 WhitespaceFormatKind::Indent { use_prev: true, remove_if_pure }
49 ), }
50 }
51
52 fn is_empty(&mut self) -> bool {
54 self.0.first.0.is_empty() && self.0.rest.is_empty()
55 }
56
57 fn is_pure(&mut self) -> bool {
59 self.0.rest.is_empty()
60 }
61
62 fn join_suffix(&mut self, other: Self) {
64 let lhs = &mut *self.0;
65 let mut rhs = other.0.take();
66
67 let lhs_last = match lhs.rest.last_mut() {
68 Some((_, last)) => last,
69 None => &mut lhs.first,
70 };
71
72 replace_with::replace_with_or_abort(
73 &mut lhs_last.0,
74 |lhs_last| AstStr::join(lhs_last, rhs.first.0)
75 );
76 lhs.rest.append(&mut rhs.rest);
77 }
78
79 fn join_prefix(&mut self, mut other: Self) {
81 replace_with::replace_with_or_abort(self, |this| {
82 other.join_suffix(this);
83 other
84 });
85 }
86}
87
88impl Formattable for Whitespace {
89 fn with_prefix_ws<O>(
90 &mut self,
91 ctx: &mut crate::Context,
92 f: &mut impl FnMut(&mut Self,&mut crate::Context) -> O,
93 ) -> Result<O, ControlFlow<()>> {
94 Ok(f(self, ctx))
95 }
96
97 fn with_strings<O>(
98 &mut self,
99 ctx: &mut crate::Context,
100 exclude_prefix_ws: bool,
101 f: &mut impl FnMut(&mut AstStr,&mut crate::Context) -> ControlFlow<O>,
102 ) -> ControlFlow<O, bool> {
103 let is_empty = self.0.first.0.is_empty() && self.0.rest.is_empty();
104
105 if !exclude_prefix_ws {
106 f(&mut self.0.first.0, ctx)?;
107 for (comment, pure) in &mut self.0.rest {
108 match comment {
109 Comment::Line(comment) => f(&mut comment.0, ctx)?,
110 Comment::Block(comment) => f(&mut comment.0, ctx)?,
111 }
112 f(&mut pure.0, ctx)?;
113 }
114 }
115
116 ControlFlow::Continue(is_empty)
117 }
118
119 fn format_output(&mut self, ctx: &mut crate::Context) -> FormatOutput {
120 let mut output = self.0.first.0.format_output(ctx);
121 for (comment, ws) in &mut self.0.rest {
122 match comment {
123 Comment::Line(comment) => comment.0
124 .format_output(ctx)
125 .append_to(&mut output),
126 Comment::Block(comment) => comment.0
127 .format_output(ctx)
128 .append_to(&mut output),
129 }
130
131 ws.0.format_output(ctx).append_to(&mut output);
132 }
133 output.prefix_ws_len = Some(output.len);
134 if let Some(multiline) = &mut output.multiline {
135 multiline.prefix_ws_len = Some(multiline.prefix_len);
136 }
137
138 output
139 }
140}
141
142impl Format<(), ()> for Whitespace {
148 fn format(
149 &mut self,
150 _ctx: &mut crate::Context,
151 _prefix_ws: (),
152 _args: ()
153 ) -> FormatOutput {
154 if !self.is_empty() {
155 tracing::warn!("Whitespace was not empty");
156 }
157 FormatOutput::default()
158 }
159}
160
161impl Format<WhitespaceConfig, ()> for Whitespace {
162 fn format(
163 &mut self,
164 ctx: &mut crate::Context,
165 prefix_ws: WhitespaceConfig,
166 _args: ()
167 ) -> FormatOutput {
168 if let Some(format) = prefix_ws.format {
169 self::format(self, ctx, format);
170 }
171
172 self.format_output(ctx)
173 }
174}
175
176#[derive(Clone, Copy, Debug)]
177#[derive(strum::EnumIs)]
178#[doc(hidden)]
179pub enum WhitespaceFormatKind {
180 Remove,
181
182 Spaces {
183 len: u16,
185 },
186
187 Indent {
188 use_prev: bool,
190
191 remove_if_pure: bool,
193 },
194}
195
196impl WhitespaceFormatKind {
197 fn indent_str_nl(
200 ctx: &mut crate::Context,
201 cur_str: &AstStr,
202 after_newline: bool
203 ) -> AstStrRepr {
204 let min_newlines = ctx.config().min_empty_lines;
205 let max_newlines = ctx.config().max_empty_lines;
206 let (min_newlines, max_newlines) = match after_newline {
207 true => (min_newlines, max_newlines),
208 false => (min_newlines + 1, max_newlines + 1),
209 };
210 let newlines = match cur_str.input() {
213 Some(input) => rustidy_util::str_count_newlines(input),
214 None => cur_str.count_newlines(),
215 };
216 let newlines = newlines.clamp(min_newlines, max_newlines);
217
218 AstStrRepr::Indentation {
219 indent: Arc::clone(&ctx.config().indent),
220 newlines,
221 depth: ctx.indent(),
222 }
223 }
224
225 fn prefix_str(
227 self,
228 ctx: &mut crate::Context,
229 cur_str: &AstStr,
230 is_last: bool,
231 after_newline: bool
232 ) -> AstStrRepr {
233 match self {
234 Self::Remove => "".into(),
235 Self::Spaces { len } => AstStrRepr::Spaces { len },
236 Self::Indent { use_prev, remove_if_pure } => match remove_if_pure && is_last {
237 true => "".into(),
238 false => ctx.with_indent_offset_if(
239 -1,
240 use_prev && is_last,
241 |ctx| Self::indent_str_nl(ctx, cur_str, after_newline)
242 ),
243 },
244 }
245 }
246
247 fn after_newline_str(
249 self,
250 ctx: &mut crate::Context,
251 cur_str: &AstStr,
252 is_last: bool
253 ) -> AstStrRepr {
254 match self {
255 Self::Remove | Self::Spaces { .. } => "".into(),
256 Self::Indent { use_prev, .. } => match is_last {
257 true => ctx.with_indent_offset_if(
258 -1,
259 use_prev,
260 |ctx| Self::indent_str_nl(ctx, cur_str, true)
261 ),
262 false => Self::indent_str_nl(ctx, cur_str, true),
263 },
264 }
265 }
266
267 fn normal_str(
269 self,
270 ctx: &mut crate::Context,
271 cur_str: &AstStr,
272 is_last: bool
273 ) -> AstStrRepr {
274 match self {
275 Self::Remove => "".into(),
276 Self::Spaces { len } => AstStrRepr::Spaces { len },
277 Self::Indent { use_prev, .. } => match is_last {
278 true => ctx.with_indent_offset_if(
279 -1,
280 use_prev,
281 |ctx| Self::indent_str_nl(ctx, cur_str, false)
282 ),
283 false => Self::indent_str_nl(ctx, cur_str, false),
284 },
285 }
286 }
287}
288
289#[doc(hidden)]
290pub fn format(
291 ws: &mut Whitespace,
292 ctx: &mut crate::Context,
293 kind: WhitespaceFormatKind
294) {
295 let after_newline = ctx.remove_tag(FormatTag::AfterNewline);
299
300 let prefix_str = kind.prefix_str(
301 ctx,
302 &ws.0.first.0,
303 ws.0.rest.is_empty(),
304 after_newline
305 );
306 ws.0.first.0.replace(prefix_str);
307
308 for (pos, (comment, ws)) in ws.0.rest.iter_mut().with_position() {
309 let is_last = matches!(pos, itertools::Position::Last | itertools::Position::Only);
310 let ws_str = match comment.is_line() {
311 true => kind.after_newline_str(ctx, &ws.0, is_last),
312 false => kind.normal_str(ctx, &ws.0, is_last),
313 };
314 ws.0.replace(ws_str);
315
316 if is_last && let Comment::Line(comment) = comment && !comment.0.has_newlines() {
317 let mut s = comment.0.str().into_owned();
318 s.push('\n');
319 comment.0
320 .replace(AstStrRepr::String(s.into()));
321 }
322 }
323}