1use rustidy_util::ast_str::AstStrRepr;
5
6#[derive(PartialEq, Eq, Clone, Copy, Debug)]
8#[must_use = "Should not ignore format output"]
9pub struct FormatOutput {
10 pub prefix_ws_len: Option<usize>,
12
13 pub len: usize,
15
16 pub is_empty: bool,
18
19 pub is_blank: bool,
21
22 pub multiline: Option<FormatMultilineOutput>,
24}
25
26impl FormatOutput {
27 #[must_use]
29 pub const fn has_prefix_ws(&self) -> bool {
30 self.prefix_ws_len.is_some()
31 }
32
33 #[must_use]
36 pub fn len_without_prefix_ws(&self) -> usize {
37 self.len - self.prefix_ws_len.unwrap_or(0)
38 }
39
40 #[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 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 pub const fn append(&mut self, other: Self) {
66 *self = Self::join(*self, other);
67 }
68
69 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#[derive(PartialEq, Eq, Clone, Copy, Debug)]
106pub struct FormatMultilineOutput {
107 pub prefix_ws_len: Option<usize>,
109
110 pub prefix_len: usize,
112
113 pub suffix_len: usize,
115}
116
117impl FormatMultilineOutput {
118 #[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 #[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 #[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
200const 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}