use crate::errors::Result;
use crate::measure::{Measurable, MeasureOptions, Measurement, cell_len};
use crate::segment::{Segment, Segments};
use crate::style::Style;
use crate::text::Text;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TreeGuides {
pub vertical: &'static str,
pub branch: &'static str,
pub last_branch: &'static str,
pub horizontal: &'static str,
pub space: &'static str,
}
impl TreeGuides {
pub const ASCII: Self = Self {
vertical: "|",
branch: "+--",
last_branch: "+--",
horizontal: "-",
space: " ",
};
pub const UNICODE: Self = Self {
vertical: "│",
branch: "├──",
last_branch: "└──",
horizontal: "─",
space: " ",
};
pub const ROUNDED: Self = Self {
vertical: "│",
branch: "├──",
last_branch: "╰──",
horizontal: "─",
space: " ",
};
pub const BOLD: Self = Self {
vertical: "┃",
branch: "┣━━",
last_branch: "┗━━",
horizontal: "━",
space: " ",
};
pub const DOUBLE: Self = Self {
vertical: "║",
branch: "╠══",
last_branch: "╚══",
horizontal: "═",
space: " ",
};
}
impl Default for TreeGuides {
fn default() -> Self {
Self::UNICODE
}
}
#[derive(Debug, Clone)]
pub struct TreeNode {
label: Text,
children: Vec<TreeNode>,
style: Option<Style>,
guide_style: Option<Style>,
expanded: bool,
}
impl TreeNode {
#[inline]
#[must_use]
pub fn new(label: impl Into<Text>) -> Self {
Self {
label: label.into(),
children: Vec::new(),
style: None,
guide_style: None,
expanded: true,
}
}
#[inline]
pub fn add_child(&mut self, child: Self) {
self.children.push(child);
}
#[inline]
#[must_use]
pub fn with_child(mut self, child: Self) -> Self {
self.children.push(child);
self
}
#[inline]
#[must_use]
pub fn style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
#[inline]
#[must_use]
pub fn guide_style(mut self, style: Style) -> Self {
self.guide_style = Some(style);
self
}
#[inline]
#[must_use]
pub const fn expanded(mut self, expanded: bool) -> Self {
self.expanded = expanded;
self
}
#[inline]
#[must_use]
pub fn label(&self) -> &Text {
&self.label
}
#[inline]
#[must_use]
pub fn children(&self) -> &[Self] {
&self.children
}
#[inline]
#[must_use]
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
}
impl From<&str> for TreeNode {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for TreeNode {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[derive(Debug, Clone)]
pub struct Tree {
root: TreeNode,
guides: TreeGuides,
guide_style: Option<Style>,
hide_root: bool,
}
impl Tree {
#[inline]
#[must_use]
pub fn new(label: impl Into<Text>) -> Self {
Self {
root: TreeNode::new(label),
guides: TreeGuides::default(),
guide_style: None,
hide_root: false,
}
}
#[inline]
#[must_use]
pub const fn from_node(root: TreeNode) -> Self {
Self {
root,
guides: TreeGuides::UNICODE,
guide_style: None,
hide_root: false,
}
}
#[inline]
#[must_use]
pub fn guides(mut self, guides: TreeGuides) -> Self {
self.guides = guides;
self
}
#[inline]
#[must_use]
pub fn guide_style(mut self, style: Style) -> Self {
self.guide_style = Some(style);
self
}
#[inline]
#[must_use]
pub const fn hide_root(mut self, hide: bool) -> Self {
self.hide_root = hide;
self
}
#[inline]
pub fn root_mut(&mut self) -> &mut TreeNode {
&mut self.root
}
#[inline]
pub fn add(&mut self, child: TreeNode) {
self.root.add_child(child);
}
#[must_use]
pub fn render(&self) -> Segments {
let mut segments = Segments::new();
if !self.hide_root {
let label_segments = self.root.label.to_segments();
if let Some(ref style) = self.root.style {
for seg in label_segments.iter() {
let combined = seg
.style
.clone()
.map(|s| s.combine(style))
.unwrap_or_else(|| style.clone());
segments.push(Segment::styled(seg.text.clone(), combined));
}
} else {
segments.extend(label_segments);
}
segments.push(Segment::newline());
}
let child_count = self.root.children.len();
for (idx, child) in self.root.children.iter().enumerate() {
let is_last = idx == child_count.saturating_sub(1);
self.render_node(&mut segments, child, "", is_last);
}
segments
}
fn render_node(&self, segments: &mut Segments, node: &TreeNode, prefix: &str, is_last: bool) {
let guide_style = node
.guide_style
.clone()
.or_else(|| self.guide_style.clone());
let branch = if is_last {
self.guides.last_branch
} else {
self.guides.branch
};
if let Some(ref style) = guide_style {
segments.push(Segment::styled(prefix.to_owned(), style.clone()));
segments.push(Segment::styled(branch.to_owned(), style.clone()));
} else {
segments.push(Segment::new(prefix));
segments.push(Segment::new(branch));
}
segments.push(Segment::new(" "));
let label_segments = node.label.to_segments();
if let Some(ref style) = node.style {
for seg in label_segments.iter() {
let combined = seg
.style
.clone()
.map(|s| s.combine(style))
.unwrap_or_else(|| style.clone());
segments.push(Segment::styled(seg.text.clone(), combined));
}
} else {
segments.extend(label_segments);
}
segments.push(Segment::newline());
if node.expanded {
let new_prefix = if is_last {
format!("{}{}", prefix, self.guides.space)
} else {
format!("{}{} ", prefix, self.guides.vertical)
};
let child_count = node.children.len();
for (idx, child) in node.children.iter().enumerate() {
let child_is_last = idx == child_count.saturating_sub(1);
self.render_node(segments, child, &new_prefix, child_is_last);
}
}
}
}
impl Measurable for Tree {
fn measure(&self, options: &MeasureOptions) -> Result<Measurement> {
fn max_width(node: &TreeNode, depth: usize, guides: &TreeGuides) -> usize {
let guide_width = if depth > 0 {
depth.saturating_mul(cell_len(guides.branch).saturating_add(1))
} else {
0
};
let label_width = cell_len(node.label.plain());
let this_width = guide_width.saturating_add(label_width);
let child_max = node
.children
.iter()
.map(|c| max_width(c, depth.saturating_add(1), guides))
.max()
.unwrap_or(0);
this_width.max(child_max)
}
let width = max_width(&self.root, 0, &self.guides);
Ok(Measurement::fixed(width).clamp_max(options.max_width))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::{Color, StandardColor};
#[test]
fn test_tree_new() {
let tree = Tree::new("Root");
assert!(!tree.hide_root);
assert!(tree.guide_style.is_none());
}
#[test]
fn test_tree_add() {
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Child 1"));
tree.add(TreeNode::new("Child 2"));
assert_eq!(tree.root.children.len(), 2);
}
#[test]
fn test_tree_render() {
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Child 1"));
tree.add(TreeNode::new("Child 2"));
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("Root"));
assert!(text.contains("Child 1"));
assert!(text.contains("Child 2"));
}
#[test]
fn test_tree_nested() {
let mut tree = Tree::new("Root");
let child = TreeNode::new("Child").with_child(TreeNode::new("Grandchild"));
tree.add(child);
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("Grandchild"));
}
#[test]
fn test_tree_guides() {
let tree = Tree::new("Root").guides(TreeGuides::ASCII);
assert_eq!(tree.guides.branch, "+--");
}
#[test]
fn test_tree_guides_unicode() {
let tree = Tree::new("Root").guides(TreeGuides::UNICODE);
assert_eq!(tree.guides.branch, "├──");
}
#[test]
fn test_tree_node_builder() {
let style = Style::new().bold();
let node = TreeNode::new("Label").style(style.clone()).expanded(false);
assert!(node.style.is_some());
assert!(!node.expanded);
}
#[test]
fn test_tree_node_expanded_default() {
let node = TreeNode::new("Label");
assert!(node.expanded); }
#[test]
fn test_tree_node_add_child() {
let mut node = TreeNode::new("Parent");
node.add_child(TreeNode::new("Child 1"));
node.add_child(TreeNode::new("Child 2"));
assert_eq!(node.children.len(), 2);
}
#[test]
fn test_tree_node_with_child() {
let node = TreeNode::new("Parent")
.with_child(TreeNode::new("Child 1"))
.with_child(TreeNode::new("Child 2"));
assert_eq!(node.children.len(), 2);
}
#[test]
fn test_tree_node_label() {
let node = TreeNode::new("My Label");
assert_eq!(node.label().plain(), "My Label");
}
#[test]
fn test_tree_node_children() {
let node = TreeNode::new("Parent").with_child(TreeNode::new("Child"));
assert_eq!(node.children().len(), 1);
}
#[test]
fn test_tree_node_has_children() {
let node1 = TreeNode::new("Empty");
assert!(!node1.has_children());
let node2 = TreeNode::new("Parent").with_child(TreeNode::new("Child"));
assert!(node2.has_children());
}
#[test]
fn test_tree_node_guide_style() {
let style = Style::new().with_color(Color::Standard(StandardColor::Blue));
let node = TreeNode::new("Label").guide_style(style);
assert!(node.guide_style.is_some());
}
#[test]
fn test_tree_from_node() {
let node = TreeNode::new("Root").with_child(TreeNode::new("Child"));
let tree = Tree::from_node(node);
assert_eq!(tree.root.children.len(), 1);
}
#[test]
fn test_tree_guide_style() {
let style = Style::new().with_color(Color::Standard(StandardColor::Green));
let tree = Tree::new("Root").guide_style(style);
assert!(tree.guide_style.is_some());
}
#[test]
fn test_tree_hide_root() {
let tree = Tree::new("Root").hide_root(true);
assert!(tree.hide_root);
}
#[test]
fn test_tree_hide_root_render() {
let mut tree = Tree::new("Root").hide_root(true);
tree.add(TreeNode::new("Child"));
let segments = tree.render();
let text = segments.plain_text();
assert!(!text.contains("Root"));
assert!(text.contains("Child"));
}
#[test]
fn test_tree_root_mut() {
let mut tree = Tree::new("Root");
tree.root_mut().add_child(TreeNode::new("Child"));
assert_eq!(tree.root.children.len(), 1);
}
#[test]
fn test_tree_render_with_guide_style() {
let style = Style::new().with_color(Color::Standard(StandardColor::Red));
let mut tree = Tree::new("Root").guide_style(style);
tree.add(TreeNode::new("Child"));
let segments = tree.render();
assert!(!segments.is_empty());
}
#[test]
fn test_tree_render_with_node_style() {
let style = Style::new().bold();
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Child").style(style));
let segments = tree.render();
assert!(!segments.is_empty());
}
#[test]
fn test_tree_render_collapsed_node() {
let mut tree = Tree::new("Root");
let child = TreeNode::new("Child")
.expanded(false)
.with_child(TreeNode::new("Grandchild"));
tree.add(child);
let segments = tree.render();
let text = segments.plain_text();
assert!(!text.contains("Grandchild"));
}
#[test]
fn test_tree_render_deep_nesting() {
let mut tree = Tree::new("Root");
let level1 = TreeNode::new("Level 1")
.with_child(TreeNode::new("Level 2").with_child(TreeNode::new("Level 3")));
tree.add(level1);
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("Level 1"));
assert!(text.contains("Level 2"));
assert!(text.contains("Level 3"));
}
#[test]
fn test_tree_render_multiple_branches() {
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Branch 1").with_child(TreeNode::new("Leaf 1")));
tree.add(TreeNode::new("Branch 2").with_child(TreeNode::new("Leaf 2")));
tree.add(TreeNode::new("Branch 3").with_child(TreeNode::new("Leaf 3")));
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("Branch 1"));
assert!(text.contains("Branch 2"));
assert!(text.contains("Branch 3"));
}
#[test]
fn test_tree_node_from_str() {
let node: TreeNode = "Test".into();
assert_eq!(node.label().plain(), "Test");
}
#[test]
fn test_tree_node_from_string() {
let node: TreeNode = String::from("Test").into();
assert_eq!(node.label().plain(), "Test");
}
#[test]
fn test_tree_guides_default() {
let guides = TreeGuides::default();
assert_eq!(guides.branch, "├──");
}
#[test]
fn test_tree_render_ascii() {
let mut tree = Tree::new("Root").guides(TreeGuides::ASCII);
tree.add(TreeNode::new("Child"));
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("+--"));
}
#[test]
fn test_tree_measure() {
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Child"));
let options = MeasureOptions::new(80);
let measurement = tree.measure(&options).ok().unwrap_or_default();
assert!(measurement.minimum > 0);
}
#[test]
fn test_tree_measure_nested() {
let mut tree = Tree::new("Root");
tree.add(TreeNode::new("Child").with_child(TreeNode::new("Very Long Grandchild Label")));
let options = MeasureOptions::new(80);
let measurement = tree.measure(&options).ok().unwrap_or_default();
assert!(measurement.minimum > 0);
}
#[test]
fn test_tree_empty() {
let tree = Tree::new("Root");
let segments = tree.render();
let text = segments.plain_text();
assert!(text.contains("Root"));
}
#[test]
fn test_tree_render_with_root_style() {
let style = Style::new().italic();
let mut tree = Tree::new("Root");
tree.root_mut().style = Some(style);
tree.add(TreeNode::new("Child"));
let segments = tree.render();
assert!(!segments.is_empty());
}
}