Skip to main content

rustidy_format/
output.rs

1//! Format output
2
3// Imports
4use rustidy_util::ast_str::AstStrRepr;
5
6/// Formatting output
7#[derive(PartialEq, Eq, Clone, Copy, Debug)]
8#[must_use = "Should not ignore format output"]
9pub struct FormatOutput {
10	/// Prefix whitespace length, if any
11	pub prefix_ws_len: Option<usize>,
12
13	/// Total length of this type
14	pub len:           usize,
15
16	/// Whether the type was empty
17	pub is_empty:      bool,
18
19	/// Whether the type was blank
20	pub is_blank:      bool,
21
22	/// Multi-line output
23	pub multiline:     Option<FormatMultilineOutput>,
24}
25
26impl FormatOutput {
27	/// Returns if this format output has any prefix whitespace
28	#[must_use]
29	pub const fn has_prefix_ws(&self) -> bool {
30		self.prefix_ws_len.is_some()
31	}
32
33	/// Returns the length of this type, excluding the prefix whitespace, if any
34	// TODO: Rename this to just `len` and `Self::len` to `total_len`?.
35	#[must_use]
36	pub fn len_without_prefix_ws(&self) -> usize {
37		self.len - self.prefix_ws_len.unwrap_or(0)
38	}
39
40	/// Returns the non-whitespace non-multiline whitespace length of this type
41	#[must_use]
42	pub fn len_non_multiline_ws(&self) -> usize {
43		match self.multiline {
44			Some(multiline) => multiline.prefix_len + multiline.suffix_len,
45			None => self.len_without_prefix_ws(),
46		}
47	}
48
49	/// Joins two format outputs.
50	///
51	/// You must ensure that `rhs` directly follows `lhs`.
52	pub const fn join(lhs: Self, rhs: Self) -> Self {
53		Self {
54			prefix_ws_len: self::join_prefix_ws(lhs.prefix_ws_len, rhs.prefix_ws_len, lhs.len),
55			len: lhs.len + rhs.len,
56			is_empty: lhs.is_empty && rhs.is_empty,
57			is_blank: lhs.is_blank && rhs.is_blank,
58			multiline: FormatMultilineOutput::join(lhs.multiline, rhs.multiline, lhs.len, rhs.len),
59		}
60	}
61
62	/// Appends a format output to this one.
63	///
64	/// See [`join`](Self::join) for details.
65	pub const fn append(&mut self, other: Self) {
66		*self = Self::join(*self, other);
67	}
68
69	/// Appends this format output to `output`.
70	///
71	/// See [`join`](Self::join) for details.
72	pub const fn append_to(self, output: &mut Self) {
73		output.append(self);
74	}
75}
76
77impl<const N: usize> From<[Self; N]> for FormatOutput {
78	fn from(outputs: [Self; N]) -> Self {
79		outputs.into_iter().collect()
80	}
81}
82
83impl FromIterator<Self> for FormatOutput {
84	fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
85		iter
86			.into_iter()
87			.fold(Self::default(), Self::join)
88	}
89}
90
91impl Default for FormatOutput {
92	fn default() -> Self {
93		Self {
94			prefix_ws_len: None,
95			len: 0,
96			is_empty: true,
97			is_blank: true,
98			multiline: None,
99		}
100	}
101}
102
103
104/// Formatting multi-line output
105#[derive(PartialEq, Eq, Clone, Copy, Debug)]
106pub struct FormatMultilineOutput {
107	/// Prefix whitespace length (before the first newline)
108	pub prefix_ws_len: Option<usize>,
109
110	/// Prefix length (before the first newline)
111	pub prefix_len:    usize,
112
113	/// Suffix length (after the last newline)
114	pub suffix_len:    usize,
115}
116
117impl FormatMultilineOutput {
118	/// Gets the multi-line output of a string
119	#[must_use]
120	#[expect(clippy::should_implement_trait, reason = "The trait semantics are different")]
121	pub fn from_str(s: &str) -> Option<Self> {
122		let (prefix, _) = s.split_once('\n')?;
123		let (_, suffix) = s.rsplit_once('\n')?;
124
125		Some(Self {
126			prefix_ws_len: None,
127			prefix_len: prefix.len(),
128			suffix_len: suffix.len(),
129		})
130	}
131
132	/// Joins two multiline outputs.
133	#[must_use]
134	pub const fn join(
135		lhs: Option<Self>,
136		rhs: Option<Self>,
137		lhs_len: usize,
138		rhs_len: usize
139	) -> Option<Self> {
140		match (lhs, rhs) {
141			(Some(lhs), Some(rhs)) => Some(Self {
142				prefix_ws_len: self::join_prefix_ws(
143					lhs.prefix_ws_len,
144					rhs.prefix_ws_len,
145					lhs.prefix_len
146				),
147				prefix_len: lhs.prefix_len,
148				suffix_len: rhs.suffix_len
149			}),
150			(Some(lhs), None) => Some(Self {
151				prefix_ws_len: lhs.prefix_ws_len,
152				prefix_len: lhs.prefix_len,
153				suffix_len: lhs.suffix_len + rhs_len,
154			}),
155			(None, Some(rhs)) => Some(Self {
156				prefix_ws_len: match lhs_len == 0 {
157					true => rhs.prefix_ws_len,
158					false => None,
159				},
160				prefix_len: rhs.prefix_len + lhs_len,
161				suffix_len: rhs.suffix_len
162			}),
163			(None, None) => None,
164		}
165	}
166
167	/// Gets the multi-line output of an ast string repr
168	#[must_use]
169	pub fn from_ast_str_repr(repr: &AstStrRepr) -> Option<Self> {
170		match *repr {
171			AstStrRepr::String(ref s) => Self::from_str(s),
172			AstStrRepr::Static(s) => Self::from_str(s),
173			AstStrRepr::Char(ch) => match ch == '\n' {
174				true => Some(Self {
175					prefix_ws_len: None,
176					prefix_len: 0,
177					suffix_len: 0,
178				}),
179				false => None,
180			},
181			AstStrRepr::Spaces { .. } => None,
182			AstStrRepr::Indentation { ref indent, newlines, depth } => match newlines {
183				0 => None,
184				_ => Some(Self {
185					prefix_ws_len: None,
186					prefix_len: 0,
187					suffix_len: depth * indent.len(),
188				})
189			},
190			AstStrRepr::Join { ref lhs, ref rhs } => Self::join(
191				Self::from_ast_str_repr(lhs.repr()),
192				Self::from_ast_str_repr(rhs.repr()),
193				lhs.len(),
194				rhs.len(),
195			),
196		}
197	}
198}
199
200/// Joins two prefix whitespace lengths
201const fn join_prefix_ws(
202	lhs_prefix_ws_len: Option<usize>,
203	rhs_prefix_ws_len: Option<usize>,
204	lhs_len: usize,
205) -> Option<usize> {
206	match lhs_prefix_ws_len {
207		Some(prefix_ws_len) => Some(prefix_ws_len),
208		None => match lhs_len == 0 {
209			true => rhs_prefix_ws_len,
210			false => None,
211		},
212	}
213}