1use std::fmt::{self, Write};
7use crate::Span;
8use super::TreeDisplay;
9
10pub struct TreeFormatter {
11 buffer: String,
12 indent_stack: Vec<bool>, }
14
15impl TreeFormatter {
16 pub fn new() -> Self {
18 TreeFormatter {
19 buffer: String::new(),
20 indent_stack: Vec::new(),
21 }
22 }
23
24 pub fn finish(self) -> String {
26 self.buffer
27 }
28
29 fn write_line(&mut self, is_last: bool, content: &str) -> fmt::Result {
31 for &continues in &self.indent_stack {
33 if continues {
34 write!(self.buffer, "│ ")?;
35 } else {
36 write!(self.buffer, " ")?;
37 }
38 }
39
40 if is_last {
42 write!(self.buffer, "└── ")?;
43 } else {
44 write!(self.buffer, "├── ")?;
45 }
46
47 writeln!(self.buffer, "{}", content)?;
49 Ok(())
50 }
51
52 pub fn root(&mut self, content: &str) -> fmt::Result {
55 if self.indent_stack.is_empty() {
56 writeln!(self.buffer, "{}", content)?;
58 } else {
59 self.node(content)?;
61 }
62 Ok(())
63 }
64
65 fn node(&mut self, content: &str) -> fmt::Result {
67 for &continues in &self.indent_stack {
69 if continues {
70 write!(self.buffer, "│ ")?;
71 } else {
72 write!(self.buffer, " ")?;
73 }
74 }
75
76 writeln!(self.buffer, "{}", content)?;
78 Ok(())
79 }
80
81 pub fn field(&mut self, is_last: bool, name: &str, value: &str) -> fmt::Result {
83 self.write_line(is_last, &format!("{}: {}", name, value))
84 }
85
86 pub fn field_with_child<T: TreeDisplay>(
88 &mut self,
89 is_last: bool,
90 name: &str,
91 value: &T,
92 source: &str,
93 ) -> fmt::Result {
94 self.write_line(is_last, name)?;
95 self.indent_stack.push(!is_last);
96 value.tree_display(self, source)?;
97 self.indent_stack.pop();
98 Ok(())
99 }
100
101 pub fn field_option<T: TreeDisplay>(
103 &mut self,
104 is_last: bool,
105 name: &str,
106 value: &Option<T>,
107 source: &str,
108 ) -> fmt::Result {
109 match value {
110 Some(v) => {
111 self.write_line(is_last, &format!("{}: Some", name))?;
112 self.indent_stack.push(!is_last);
113 v.tree_display(self, source)?;
114 self.indent_stack.pop();
115 }
116 None => {
117 self.write_line(is_last, &format!("{}: None", name))?;
118 }
119 }
120 Ok(())
121 }
122
123 pub fn field_vec<T: TreeDisplay>(
125 &mut self,
126 is_last: bool,
127 name: &str,
128 items: &[T],
129 source: &str,
130 ) -> fmt::Result {
131 self.write_line(is_last, &format!("{}: Vec ({} items)", name, items.len()))?;
132 if !items.is_empty() {
133 self.indent_stack.push(!is_last);
134 for (i, item) in items.iter().enumerate() {
135 let is_last_item = i == items.len() - 1;
136 self.write_line(is_last_item, &format!("[{}]", i))?;
137 self.indent_stack.push(!is_last_item);
138 item.tree_display(self, source)?;
139 self.indent_stack.pop();
140 }
141 self.indent_stack.pop();
142 }
143 Ok(())
144 }
145
146 pub fn field_array<T: TreeDisplay, const N: usize>(
148 &mut self,
149 is_last: bool,
150 name: &str,
151 items: &[T; N],
152 source: &str,
153 ) -> fmt::Result {
154 self.field_vec(is_last, name, items, source)
155 }
156
157 pub fn field_tuple2<T1: TreeDisplay, T2: TreeDisplay>(
159 &mut self,
160 is_last: bool,
161 name: &str,
162 value: &(T1, T2),
163 source: &str,
164 ) -> fmt::Result {
165 self.write_line(is_last, &format!("{}: (2 items)", name))?;
166 self.indent_stack.push(!is_last);
167
168 self.write_line(false, "[0]")?;
169 self.indent_stack.push(true);
170 value.0.tree_display(self, source)?;
171 self.indent_stack.pop();
172
173 self.write_line(true, "[1]")?;
174 self.indent_stack.push(false);
175 value.1.tree_display(self, source)?;
176 self.indent_stack.pop();
177
178 self.indent_stack.pop();
179 Ok(())
180 }
181
182 pub fn format_raw(&self, span: Span, source: &str) -> String {
188 let raw = extract_span_text(span, source);
189 let normalized = raw.split_whitespace().collect::<Vec<_>>().join(" ");
191 truncate_with_ellipsis(&normalized, 40)
192 }
193}
194
195impl Default for TreeFormatter {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201fn extract_span_text(span: Span, source: &str) -> String {
203 if span.start <= span.end && span.end <= source.len() {
204 source[span.start..span.end].to_string()
205 } else {
206 format!("<invalid span: {}..{}>", span.start, span.end)
207 }
208}
209
210pub(crate) fn truncate_with_ellipsis(s: &str, max_len: usize) -> String {
215 if s.len() <= max_len {
216 s.to_string()
217 } else {
218 let half = max_len / 2;
219 let start = &s[..half.min(s.len())];
220 let end_start = s.len().saturating_sub(half);
221 let end = &s[end_start..];
222 format!("{}...{}", start, end)
223 }
224}