1use crate::console::Justify;
10use crate::console::{Console, ConsoleOptions};
11use crate::measure::Measurement;
12use crate::protocol::Renderable;
13use crate::segment::Segment;
14use crate::style::{Style, StyleType};
15use crate::table::Cell;
16
17pub const ASCII_GUIDES: [&str; 4] = [" ", "| ", "+-- ", "`-- "];
19pub const TREE_GUIDES: [[&str; 4]; 3] = [
21 [" ", "│ ", "├── ", "└── "],
22 [" ", "┃ ", "┣━━ ", "┗━━ "],
23 [" ", "║ ", "╠══ ", "╚══ "],
24];
25
26const SPACE: usize = 0;
28const CONTINUE: usize = 1;
29const FORK: usize = 2;
30const END: usize = 3;
31
32pub struct Tree {
34 label: Cell,
35 style: StyleType,
36 guide_style: StyleType,
37 children: Vec<Tree>,
38 expanded: bool,
39 highlight: bool,
40 hide_root: bool,
41}
42
43impl Tree {
44 pub fn new(label: impl Into<Cell>) -> Self {
48 Tree {
49 label: label.into(),
50 style: StyleType::Name("tree".to_string()),
51 guide_style: StyleType::Name("tree.line".to_string()),
52 children: Vec::new(),
53 expanded: true,
54 highlight: false,
55 hide_root: false,
56 }
57 }
58
59 pub fn highlight(mut self, highlight: bool) -> Self {
62 self.highlight = highlight;
63 self
64 }
65
66 pub fn style(mut self, style: impl Into<StyleType>) -> Self {
69 self.style = style.into();
70 self
71 }
72
73 pub fn guide_style(mut self, style: impl Into<StyleType>) -> Self {
77 self.guide_style = style.into();
78 self
79 }
80
81 pub fn expanded(mut self, expanded: bool) -> Self {
84 self.expanded = expanded;
85 self
86 }
87
88 pub fn hide_root(mut self, hide_root: bool) -> Self {
91 self.hide_root = hide_root;
92 self
93 }
94
95 pub fn set_style(&mut self, style: impl Into<StyleType>) -> &mut Self {
97 self.style = style.into();
98 self
99 }
100
101 pub fn set_guide_style(&mut self, style: impl Into<StyleType>) -> &mut Self {
103 self.guide_style = style.into();
104 self
105 }
106
107 pub fn set_expanded(&mut self, expanded: bool) -> &mut Self {
110 self.expanded = expanded;
111 self
112 }
113
114 pub fn set_hide_root(&mut self, hide_root: bool) -> &mut Self {
116 self.hide_root = hide_root;
117 self
118 }
119
120 pub fn set_highlight(&mut self, highlight: bool) -> &mut Self {
122 self.highlight = highlight;
123 self
124 }
125
126 pub fn set_label(&mut self, label: impl Into<Cell>) -> &mut Self {
128 self.label = label.into();
129 self
130 }
131
132 pub fn label(&self) -> &Cell {
134 &self.label
135 }
136
137 pub fn get_style(&self) -> &StyleType {
139 &self.style
140 }
141
142 pub fn get_guide_style(&self) -> &StyleType {
144 &self.guide_style
145 }
146
147 pub fn is_expanded(&self) -> bool {
149 self.expanded
150 }
151
152 pub fn is_root_hidden(&self) -> bool {
154 self.hide_root
155 }
156
157 pub fn is_highlighted(&self) -> bool {
159 self.highlight
160 }
161
162 pub fn children(&self) -> &[Tree] {
164 &self.children
165 }
166
167 pub fn children_mut(&mut self) -> &mut Vec<Tree> {
169 &mut self.children
170 }
171
172 pub fn add(&mut self, label: impl Into<Cell>) -> &mut Tree {
176 let mut child = Tree::new(label);
177 child.style = self.style.clone();
178 child.guide_style = self.guide_style.clone();
179 self.add_tree(child)
180 }
181
182 pub fn add_tree(&mut self, child: Tree) -> &mut Tree {
186 self.children.push(child);
187 self.children.last_mut().expect("just pushed a child")
188 }
189
190 fn make_guide(options: &ConsoleOptions, index: usize, style: Style) -> Segment {
192 let line = if options.ascii_only() {
193 ASCII_GUIDES[index]
194 } else {
195 let guide = if style.attr(BOLD) == Some(true) {
196 1
197 } else if style.attr(UNDERLINE2) == Some(true) {
198 2
199 } else {
200 0
201 };
202 TREE_GUIDES[if options.legacy_windows { 0 } else { guide }][index]
203 };
204 Segment::new(line, Some(style))
205 }
206}
207
208const BOLD: usize = 0;
210const UNDERLINE2: usize = 9;
211
212struct Label<'a> {
215 label: &'a Cell,
216 style: &'a Style,
217 highlight: bool,
218}
219
220impl Renderable for Label<'_> {
221 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
222 let segments = match self.label {
223 Cell::Renderable(renderable) => renderable.rich_render(console, options),
224 cell => cell
225 .to_text(console, Some(self.highlight))
226 .unwrap_or_default()
227 .rich_render(console, options),
228 };
229 if self.style.is_null() {
230 segments
231 } else {
232 Segment::apply_style(&segments, self.style)
233 }
234 }
235}
236
237impl Tree {
238 fn render_lines_into(&self, console: &Console, options: &ConsoleOptions) -> Vec<Vec<Segment>> {
246 let get_style = |style: &StyleType| console.get_style(style).unwrap_or_default();
247 let guide_style = get_style(&self.guide_style);
248 let mut levels: Vec<Segment> =
249 vec![Self::make_guide(options, CONTINUE, guide_style.clone())];
250 let mut stack: Vec<(&[Tree], usize)> = vec![(std::slice::from_ref(self), 0)];
251 let mut guide_style_stack = vec![guide_style];
252 let mut style_stack = vec![get_style(&self.style)];
253 let remove_guide_styles = Style::parse("not bold not underline2").unwrap_or_default();
254 let offset = if self.hide_root { 2 } else { 1 };
255 let pad = options.justify != Justify::Default;
256 let mut depth = 0usize;
257 let mut lines = Vec::new();
258 let level_style = |segment: &Segment| segment.style.clone().unwrap_or_default();
259
260 while let Some((siblings, next)) = stack.last_mut() {
261 let siblings: &[Tree] = siblings;
262 let Some(node) = siblings.get(*next) else {
263 stack.pop();
264 levels.pop();
265 if let Some(level) = levels.last_mut() {
266 let style = level_style(level);
267 *level = Self::make_guide(options, FORK, style);
268 guide_style_stack.pop();
269 style_stack.pop();
270 }
271 continue;
272 };
273 *next += 1;
274 let last = *next == siblings.len();
275 if last {
276 let level = levels.last_mut().expect("a level per open stack entry");
277 *level = Self::make_guide(options, END, level_style(level));
278 }
279
280 let current_guide = guide_style_stack.last().cloned().unwrap_or_default();
281 let current_style = style_stack.last().cloned().unwrap_or_default();
282 let node_guide_style = current_guide.combine(&get_style(&node.guide_style));
283 let style = current_style.combine(&get_style(&node.style));
284 let prefix_width = levels.len().saturating_sub(offset) * 4;
285
286 if !(depth == 0 && self.hide_root) && prefix_width < options.max_width {
287 let mut prefix: Vec<Segment> = levels[offset.min(levels.len())..].to_vec();
288 let mut label_options = options.update_width(options.max_width - prefix_width);
289 label_options.highlight = Some(self.highlight);
290 label_options.height = None;
291 let label = Label {
292 label: &node.label,
293 style: &style,
294 highlight: self.highlight,
295 };
296 let background = Style::from_color(None, style.bgcolor().cloned());
297 for (index, label_line) in console
298 .render_lines(&label, &label_options, pad)
299 .into_iter()
300 .enumerate()
301 {
302 let mut line = Vec::new();
303 if !prefix.is_empty() {
304 line.extend(prefix.iter().map(|segment| {
305 let own = segment.style.clone().unwrap_or_default();
306 let own = if background.is_null() {
307 own
308 } else {
309 background.combine(&own)
310 };
311 let style = if own.is_null() {
312 remove_guide_styles.clone()
313 } else {
314 own.combine(&remove_guide_styles)
315 };
316 Segment::new(segment.text.clone(), Some(style))
317 }));
318 }
319 line.extend(label_line);
320 lines.push(line);
321 if index == 0 && !prefix.is_empty() {
322 let level = prefix.last_mut().expect("non-empty prefix");
323 *level = Self::make_guide(
324 options,
325 if last { SPACE } else { CONTINUE },
326 level_style(level),
327 );
328 }
329 }
330 }
331
332 if node.expanded && !node.children.is_empty() {
333 let level = levels.last_mut().expect("a level per open stack entry");
334 *level = Self::make_guide(
335 options,
336 if last { SPACE } else { CONTINUE },
337 level_style(level),
338 );
339 levels.push(Self::make_guide(
340 options,
341 if node.children.len() == 1 { END } else { FORK },
342 node_guide_style,
343 ));
344 style_stack.push(current_style.combine(&get_style(&node.style)));
345 guide_style_stack.push(current_guide.combine(&get_style(&node.guide_style)));
346 stack.push((&node.children, 0));
347 depth += 1;
348 }
349 }
350 lines
351 }
352}
353
354impl Drop for Tree {
355 fn drop(&mut self) {
358 let mut pending = std::mem::take(&mut self.children);
359 while let Some(mut child) = pending.pop() {
360 pending.append(&mut child.children);
361 }
362 }
363}
364
365impl Renderable for Tree {
366 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
367 let lines = self.render_lines_into(console, options);
368 let mut segments = Vec::new();
369 let last = lines.len().saturating_sub(1);
370 for (index, line) in lines.into_iter().enumerate() {
371 if index == last && line.is_empty() {
374 segments.push(Segment::new("", None));
375 }
376 segments.extend(line);
377 if index != last {
378 segments.push(Segment::line());
379 }
380 }
381 segments
382 }
383
384 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
387 let mut width = (0, 0);
389 let mut pending: Vec<(&Tree, usize)> = vec![(self, 0)];
390 while let Some((tree, level)) = pending.pop() {
391 let label = tree.label.measure_cell(console, options);
392 let indent = level * 4;
393 width.0 = width.0.max(label.minimum + indent);
394 width.1 = width.1.max(label.maximum + indent);
395 if tree.expanded {
396 pending.extend(tree.children.iter().map(|child| (child, level + 1)));
397 }
398 }
399 Measurement::new(width.0, width.1)
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use crate::color::ColorSystem;
407
408 fn console() -> Console {
409 Console::builder()
410 .force_terminal(true)
411 .color_system(Some(ColorSystem::Truecolor))
412 .width(40)
413 .build()
414 }
415
416 #[test]
417 fn nested_tree() {
418 let mut tree = Tree::new("root");
419 let a = tree.add("child A");
420 a.add("leaf A1");
421 a.add("leaf A2");
422 tree.add("child B");
423 let out = console().render_export(&tree);
424 let expected = concat!(
425 "root\n",
426 "├── child A\n",
427 "│ ├── leaf A1\n",
428 "│ └── leaf A2\n",
429 "└── child B\n",
430 );
431 assert_eq!(out, expected);
432 }
433
434 #[test]
435 fn deep_tree_renders_without_recursion() {
436 let handle = std::thread::Builder::new()
440 .stack_size(2 * 1024 * 1024)
441 .spawn(|| {
442 let mut tree = Tree::new("0");
443 let mut node = &mut tree;
444 for depth in 1..20_000 {
445 node = node.add(depth.to_string());
446 }
447 let console = Console::builder().width(12).build();
448 let out = console.render_export(&tree);
449 let measured = Measurement::get(&console, &console.options(), &tree);
450 (out, measured)
451 })
452 .expect("spawn");
453 let (out, measured) = handle.join().expect("deep tree render overflowed");
454 assert_eq!(out, "0\n└── 1\n └── 2\n");
457 assert_eq!(measured, Measurement::new(12, 12));
459 }
460}