1use crate::console::{Console, ConsoleOptions};
12use crate::measure::Measurement;
13use crate::protocol::Renderable;
14use crate::segment::Segment;
15use crate::style::Style;
16use crate::table::Cell;
17
18const SPACE: &str = " ";
20const CONTINUE: &str = "│ ";
21const FORK: &str = "├── ";
22const END: &str = "└── ";
23
24pub struct Tree {
26 label: Cell,
27 children: Vec<Tree>,
28 highlight: bool,
29}
30
31impl Tree {
32 pub fn new(label: impl Into<Cell>) -> Self {
36 Tree {
37 label: label.into(),
38 children: Vec::new(),
39 highlight: false,
40 }
41 }
42
43 pub fn highlight(mut self, highlight: bool) -> Self {
46 self.highlight = highlight;
47 self
48 }
49
50 pub fn add(&mut self, label: impl Into<Cell>) -> &mut Tree {
53 self.children.push(Tree::new(label));
54 self.children.last_mut().expect("just pushed a child")
55 }
56
57 #[allow(clippy::too_many_arguments)]
60 fn render_label(
61 &self,
62 console: &Console,
63 options: &ConsoleOptions,
64 highlight: bool,
65 lines: &mut Vec<Vec<Segment>>,
66 prefix_first: &str,
67 prefix_rest: &str,
68 available: usize,
69 ) {
70 let guide_style = Some(Style::new());
71 let mut label_lines = if let Cell::Renderable(renderable) = &self.label {
74 let mut label_options = options.update_width(available);
75 label_options.height = None;
76 console.render_lines(renderable.as_ref(), &label_options, false)
77 } else {
78 self.label
79 .to_text(console, Some(highlight))
80 .unwrap_or_default()
81 .render_lines(console.theme(), &Style::new(), Some(available))
82 };
83 if label_lines.is_empty() {
84 label_lines.push(Vec::new());
85 }
86
87 for (index, label_line) in label_lines.into_iter().enumerate() {
88 let prefix = if index == 0 {
89 prefix_first
90 } else {
91 prefix_rest
92 };
93 let mut line = Vec::new();
94 if !prefix.is_empty() {
95 line.push(Segment::new(prefix.to_string(), guide_style.clone()));
96 }
97 line.extend(label_line);
98 lines.push(line);
99 }
100 }
101
102 fn render_into(
111 &self,
112 console: &Console,
113 options: &ConsoleOptions,
114 highlight: bool,
115 lines: &mut Vec<Vec<Segment>>,
116 width: usize,
117 ) {
118 let mut stack: Vec<(&Tree, usize)> = vec![(self, 0)];
121 let mut levels: Vec<bool> = Vec::new();
122 let visit = |node: &Tree, levels: &[bool], lines: &mut Vec<Vec<Segment>>| {
123 let guide_width = levels.len() * 4;
128 if guide_width >= width {
129 return;
130 }
131 let mut prefix_rest = String::new();
132 for &last in levels {
133 prefix_rest.push_str(if last { SPACE } else { CONTINUE });
134 }
135 let mut prefix_first = String::new();
136 if let Some((&last, parents)) = levels.split_last() {
137 for &parent_last in parents {
138 prefix_first.push_str(if parent_last { SPACE } else { CONTINUE });
139 }
140 prefix_first.push_str(if last { END } else { FORK });
141 }
142 node.render_label(
143 console,
144 options,
145 highlight,
146 lines,
147 &prefix_first,
148 &prefix_rest,
149 width - guide_width,
150 );
151 };
152 visit(self, &levels, lines);
153 while let Some((node, next)) = stack.last_mut() {
154 let node: &Tree = node;
155 if let Some(child) = node.children.get(*next) {
156 *next += 1;
157 levels.push(*next == node.children.len());
158 visit(child, &levels, lines);
159 stack.push((child, 0));
160 } else {
161 stack.pop();
162 levels.pop();
163 }
164 }
165 }
166}
167
168impl Drop for Tree {
169 fn drop(&mut self) {
172 let mut pending = std::mem::take(&mut self.children);
173 while let Some(mut child) = pending.pop() {
174 pending.append(&mut child.children);
175 }
176 }
177}
178
179impl Renderable for Tree {
180 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
181 let mut lines: Vec<Vec<Segment>> = Vec::new();
182 self.render_into(
183 console,
184 options,
185 self.highlight,
186 &mut lines,
187 options.max_width,
188 );
189
190 let mut segments = Vec::new();
191 let last = lines.len().saturating_sub(1);
192 for (index, line) in lines.into_iter().enumerate() {
193 segments.extend(line);
194 if index != last {
195 segments.push(Segment::line());
196 }
197 }
198 segments
199 }
200
201 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
204 let mut width = (0, 0);
206 let mut pending: Vec<(&Tree, usize)> = vec![(self, 0)];
207 while let Some((tree, level)) = pending.pop() {
208 let label = tree.label.measure_cell(console, options);
209 let indent = level * 4;
210 width.0 = width.0.max(label.minimum + indent);
211 width.1 = width.1.max(label.maximum + indent);
212 pending.extend(tree.children.iter().map(|child| (child, level + 1)));
213 }
214 Measurement::new(width.0, width.1)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::color::ColorSystem;
222
223 fn console() -> Console {
224 Console::builder()
225 .force_terminal(true)
226 .color_system(Some(ColorSystem::Truecolor))
227 .width(40)
228 .build()
229 }
230
231 #[test]
232 fn nested_tree() {
233 let mut tree = Tree::new("root");
234 let a = tree.add("child A");
235 a.add("leaf A1");
236 a.add("leaf A2");
237 tree.add("child B");
238 let out = console().render_export(&tree);
239 let expected = concat!(
240 "root\n",
241 "├── child A\n",
242 "│ ├── leaf A1\n",
243 "│ └── leaf A2\n",
244 "└── child B\n",
245 );
246 assert_eq!(out, expected);
247 }
248
249 #[test]
250 fn deep_tree_renders_without_recursion() {
251 let handle = std::thread::Builder::new()
255 .stack_size(2 * 1024 * 1024)
256 .spawn(|| {
257 let mut tree = Tree::new("0");
258 let mut node = &mut tree;
259 for depth in 1..20_000 {
260 node = node.add(depth.to_string());
261 }
262 let console = Console::builder().width(12).build();
263 let out = console.render_export(&tree);
264 let measured = Measurement::get(&console, &console.options(), &tree);
265 (out, measured)
266 })
267 .expect("spawn");
268 let (out, measured) = handle.join().expect("deep tree render overflowed");
269 assert_eq!(out, "0\n└── 1\n └── 2\n");
272 assert_eq!(measured, Measurement::new(12, 12));
274 }
275}