1use crate::console::{ConsoleOptions, RenderResult, Renderable};
4use crate::segment::Segment;
5use crate::style::Style;
6
7#[derive(Debug, Clone)]
13pub struct TreeGuides {
14 pub space: &'static str,
16 pub continue_line: &'static str,
18 pub fork: &'static str,
20 pub end: &'static str,
22}
23
24pub const ASCII_GUIDES: TreeGuides = TreeGuides {
26 space: " ",
27 continue_line: "| ",
28 fork: "+-- ",
29 end: "`-- ",
30};
31
32pub const TREE_GUIDES: TreeGuides = TreeGuides {
34 space: " ",
35 continue_line: "│ ",
36 fork: "├── ",
37 end: "└── ",
38};
39
40#[derive(Debug, Clone)]
46pub struct Tree {
47 pub label: String,
49 pub style: Style,
51 pub guide_style: Style,
53 pub expanded: bool,
55 pub highlight: bool,
57 pub hide_root: bool,
59 pub children: Vec<Tree>,
61}
62
63impl Tree {
64 pub fn new(label: impl Into<String>) -> Self {
66 Self {
67 label: label.into(),
68 style: Style::new(),
69 guide_style: Style::new(),
70 expanded: true,
71 highlight: false,
72 hide_root: false,
73 children: Vec::new(),
74 }
75 }
76
77 pub fn add(&mut self, label: impl Into<String>) -> &mut Tree {
79 let child = Tree::new(label);
80 self.children.push(child);
81 self.children.last_mut().unwrap()
82 }
83
84 pub fn style(mut self, style: Style) -> Self {
86 self.style = style;
87 self
88 }
89
90 pub fn guide_style(mut self, style: Style) -> Self {
92 self.guide_style = style;
93 self
94 }
95
96 pub fn hide_root(mut self) -> Self {
98 self.hide_root = true;
99 self
100 }
101}
102
103impl Renderable for Tree {
104 fn render(&self, options: &ConsoleOptions) -> RenderResult {
105 let guides = if options.ascii_only {
106 &ASCII_GUIDES
107 } else {
108 &TREE_GUIDES
109 };
110 let mut lines: Vec<Vec<Segment>> = Vec::new();
111
112 if !self.hide_root {
113 lines.push(vec![Segment::new(&self.label), Segment::line()]);
114 }
115
116 let last_idx = self.children.len().saturating_sub(1);
117 for (i, child) in self.children.iter().enumerate() {
118 let is_last = i == last_idx;
119 self.render_node(child, &mut lines, guides, "", is_last, options);
120 }
121
122 RenderResult {
123 lines,
124 items: Vec::new(),
125 }
126 }
127}
128
129impl Tree {
130 fn render_node(
131 &self,
132 node: &Tree,
133 lines: &mut Vec<Vec<Segment>>,
134 guides: &TreeGuides,
135 prefix: &str,
136 is_last: bool,
137 _options: &ConsoleOptions,
138 ) {
139 let connector = if is_last { guides.end } else { guides.fork };
140 let guide_ansi = self.guide_style.to_ansi();
141 let guide_reset = if guide_ansi.is_empty() { "" } else { "\x1b[0m" };
142
143 let guide_str = format!("{prefix}{connector}");
145 lines.push(vec![
146 Segment::new(format!("{guide_ansi}{guide_str}{guide_reset}")),
147 Segment::new(&node.label),
148 Segment::line(),
149 ]);
150
151 let child_prefix = if is_last {
153 format!("{prefix}{}", guides.space)
154 } else {
155 format!("{prefix}{}", guides.continue_line)
156 };
157 let child_prefix_styled = format!("{guide_ansi}{child_prefix}{guide_reset}");
158
159 let last_child = node.children.len().saturating_sub(1);
160 for (i, child) in node.children.iter().enumerate() {
161 let child_is_last = i == last_child;
162 self.render_node(
163 child,
164 lines,
165 guides,
166 &child_prefix_styled,
167 child_is_last,
168 _options,
169 );
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::console::ConsoleOptions;
178
179 #[test]
180 fn test_simple_tree() {
181 let mut tree = Tree::new("Root");
182 tree.add("Child 1");
183 tree.add("Child 2");
184
185 let opts = ConsoleOptions::default();
186 let result = tree.render(&opts);
187 let ansi = result.to_ansi();
188 assert!(ansi.contains("Root"));
189 assert!(ansi.contains("Child 1"));
190 assert!(ansi.contains("Child 2"));
191 }
192
193 #[test]
194 fn test_nested_tree() {
195 let mut tree = Tree::new("Root");
196 let child = tree.add("A");
197 child.add("A.1");
198 tree.add("B");
199
200 let opts = ConsoleOptions::default();
201 let result = tree.render(&opts);
202 let ansi = result.to_ansi();
203 assert!(ansi.contains("A.1"));
204 }
205}