1use std::borrow::Cow;
4
5use super::Span;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum ValueType {
10 String,
12 Number,
14 Boolean,
16 Null,
18 #[default]
20 Other,
21}
22
23#[derive(Copy, Clone, Debug, Default)]
25pub struct FormattedValue {
26 pub span: Span,
28 pub width: usize,
30 pub value_type: ValueType,
32}
33
34impl FormattedValue {
35 pub fn new(span: Span, width: usize) -> Self {
37 Self {
38 span,
39 width,
40 value_type: ValueType::Other,
41 }
42 }
43
44 pub fn with_type(span: Span, width: usize, value_type: ValueType) -> Self {
46 Self {
47 span,
48 width,
49 value_type,
50 }
51 }
52}
53
54#[derive(Clone, Debug)]
56pub enum AttrStatus {
57 Unchanged { value: FormattedValue },
59 Changed {
61 old: FormattedValue,
62 new: FormattedValue,
63 },
64 Deleted { value: FormattedValue },
66 Inserted { value: FormattedValue },
68}
69
70#[derive(Clone, Debug)]
72pub struct Attr {
73 pub name: Cow<'static, str>,
75 pub name_width: usize,
77 pub status: AttrStatus,
79}
80
81impl Attr {
82 pub fn unchanged(
84 name: impl Into<Cow<'static, str>>,
85 name_width: usize,
86 value: FormattedValue,
87 ) -> Self {
88 Self {
89 name: name.into(),
90 name_width,
91 status: AttrStatus::Unchanged { value },
92 }
93 }
94
95 pub fn changed(
97 name: impl Into<Cow<'static, str>>,
98 name_width: usize,
99 old: FormattedValue,
100 new: FormattedValue,
101 ) -> Self {
102 Self {
103 name: name.into(),
104 name_width,
105 status: AttrStatus::Changed { old, new },
106 }
107 }
108
109 pub fn deleted(
111 name: impl Into<Cow<'static, str>>,
112 name_width: usize,
113 value: FormattedValue,
114 ) -> Self {
115 Self {
116 name: name.into(),
117 name_width,
118 status: AttrStatus::Deleted { value },
119 }
120 }
121
122 pub fn inserted(
124 name: impl Into<Cow<'static, str>>,
125 name_width: usize,
126 value: FormattedValue,
127 ) -> Self {
128 Self {
129 name: name.into(),
130 name_width,
131 status: AttrStatus::Inserted { value },
132 }
133 }
134
135 pub fn is_changed(&self) -> bool {
137 matches!(self.status, AttrStatus::Changed { .. })
138 }
139
140 pub fn line_width(&self) -> usize {
143 let value_width = match &self.status {
144 AttrStatus::Unchanged { value } => value.width,
145 AttrStatus::Changed { old, new } => old.width.max(new.width),
146 AttrStatus::Deleted { value } => value.width,
147 AttrStatus::Inserted { value } => value.width,
148 };
149 self.name_width + 2 + value_width + 1
155 }
156}
157
158#[derive(Clone, Debug, Default)]
166pub struct ChangedGroup {
167 pub attr_indices: Vec<usize>,
169 pub max_name_width: usize,
171 pub max_old_width: usize,
173 pub max_new_width: usize,
175}
176
177impl ChangedGroup {
178 pub fn new() -> Self {
180 Self::default()
181 }
182
183 pub fn add(&mut self, index: usize, attr: &Attr) {
185 self.attr_indices.push(index);
186 self.max_name_width = self.max_name_width.max(attr.name_width);
187
188 if let AttrStatus::Changed { old, new } = &attr.status {
189 self.max_old_width = self.max_old_width.max(old.width);
190 self.max_new_width = self.max_new_width.max(new.width);
191 }
192 }
193
194 pub fn is_empty(&self) -> bool {
196 self.attr_indices.is_empty()
197 }
198
199 pub fn minus_line_width(&self, attrs: &[Attr]) -> usize {
202 if self.attr_indices.is_empty() {
203 return 0;
204 }
205
206 let mut width = 0;
207 for (i, &idx) in self.attr_indices.iter().enumerate() {
208 if i > 0 {
209 width += 1; }
211 let attr = &attrs[idx];
212 if let AttrStatus::Changed { .. } = &attr.status {
213 width += self.max_name_width + 2 + self.max_old_width + 1;
216 }
217 }
218 width
219 }
220
221 pub fn plus_line_width(&self, attrs: &[Attr]) -> usize {
223 if self.attr_indices.is_empty() {
224 return 0;
225 }
226
227 let mut width = 0;
228 for (i, &idx) in self.attr_indices.iter().enumerate() {
229 if i > 0 {
230 width += 1; }
232 let attr = &attrs[idx];
233 if let AttrStatus::Changed { .. } = &attr.status {
234 width += self.max_name_width + 2 + self.max_new_width + 1;
236 }
237 }
238 width
239 }
240}
241
242pub fn group_changed_attrs(
247 attrs: &[Attr],
248 max_line_width: usize,
249 indent_width: usize,
250) -> Vec<ChangedGroup> {
251 let available_width = max_line_width.saturating_sub(indent_width + 2); let mut groups = Vec::new();
254 let mut current = ChangedGroup::new();
255 let mut current_width = 0usize;
256
257 for (i, attr) in attrs.iter().enumerate() {
258 if !attr.is_changed() {
259 continue;
260 }
261
262 let attr_width = attr.line_width();
263 let needed = if current.is_empty() {
264 attr_width
265 } else {
266 attr_width + 1 };
268
269 if current_width + needed > available_width && !current.is_empty() {
270 groups.push(std::mem::take(&mut current));
272 current_width = 0;
273 }
274
275 let needed = if current.is_empty() {
276 attr_width
277 } else {
278 attr_width + 1
279 };
280 current_width += needed;
281 current.add(i, attr);
282 }
283
284 if !current.is_empty() {
285 groups.push(current);
286 }
287
288 groups
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 fn make_changed_attr(name: &'static str, old_width: usize, new_width: usize) -> Attr {
296 Attr::changed(
297 name,
298 name.len(),
299 FormattedValue::new(Span::default(), old_width),
300 FormattedValue::new(Span::default(), new_width),
301 )
302 }
303
304 #[test]
305 fn test_attr_line_width() {
306 let attr = make_changed_attr("fill", 3, 4);
308 assert_eq!(attr.line_width(), 4 + 2 + 4 + 1); }
310
311 #[test]
312 fn test_group_single_attr() {
313 let attrs = vec![make_changed_attr("fill", 3, 4)];
314 let groups = group_changed_attrs(&attrs, 80, 0);
315
316 assert_eq!(groups.len(), 1);
317 assert_eq!(groups[0].attr_indices, vec![0]);
318 }
319
320 #[test]
321 fn test_group_multiple_fit_one_line() {
322 let attrs = vec![
324 make_changed_attr("fill", 3, 4),
325 make_changed_attr("x", 2, 2),
326 ];
327 let groups = group_changed_attrs(&attrs, 80, 0);
328
329 assert_eq!(groups.len(), 1);
330 assert_eq!(groups[0].attr_indices, vec![0, 1]);
331 }
332
333 #[test]
334 fn test_group_overflow_to_second_line() {
335 let attrs = vec![
337 make_changed_attr("fill", 3, 4),
338 make_changed_attr("x", 2, 2),
339 ];
340 let groups = group_changed_attrs(&attrs, 15, 0);
345
346 assert_eq!(groups.len(), 2);
347 assert_eq!(groups[0].attr_indices, vec![0]);
348 assert_eq!(groups[1].attr_indices, vec![1]);
349 }
350
351 #[test]
352 fn test_group_skips_unchanged() {
353 let attrs = vec![
354 make_changed_attr("fill", 3, 4),
355 Attr::unchanged("x", 1, FormattedValue::new(Span::default(), 2)),
356 make_changed_attr("y", 2, 2),
357 ];
358 let groups = group_changed_attrs(&attrs, 80, 0);
359
360 assert_eq!(groups.len(), 1);
361 assert_eq!(groups[0].attr_indices, vec![0, 2]); }
363
364 #[test]
365 fn test_group_max_widths() {
366 let attrs = vec![
367 make_changed_attr("fill", 3, 4), make_changed_attr("stroke", 5, 3), ];
370 let groups = group_changed_attrs(&attrs, 80, 0);
371
372 assert_eq!(groups.len(), 1);
373 assert_eq!(groups[0].max_name_width, 6); assert_eq!(groups[0].max_old_width, 5);
375 assert_eq!(groups[0].max_new_width, 4);
376 }
377}