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