use crate::children::Children;
use crate::{default_enumerator, default_indenter, Enumerator, Indenter, Node, StyleFunc};
use lipgloss::{height, join_horizontal, join_vertical, Style, LEFT, TOP};
use unicode_width::UnicodeWidthStr;
struct DummyChildren {
len: usize,
}
impl Children for DummyChildren {
fn at(&self, _index: usize) -> Option<&dyn Node> {
None
}
fn length(&self) -> usize {
self.len
}
}
struct VisibleChildren<'a> {
base: &'a dyn Children,
map: Vec<usize>,
}
impl<'a> Children for VisibleChildren<'a> {
fn at(&self, index: usize) -> Option<&dyn Node> {
self.map.get(index).and_then(|&i| self.base.at(i))
}
fn length(&self) -> usize {
self.map.len()
}
}
#[derive(Debug, Clone)]
pub struct TreeStyle {
pub enumerator_func: StyleFunc,
pub item_func: StyleFunc,
pub enumerator_base: Option<Style>,
pub item_base: Option<Style>,
pub root: Style,
}
impl Default for TreeStyle {
fn default() -> Self {
Self {
enumerator_func: |_children, _i| Style::new().padding_right(1),
item_func: |_children, _i| Style::new(),
enumerator_base: None,
item_base: None,
root: Style::new(),
}
}
}
#[derive(Clone)]
pub struct Renderer {
style: TreeStyle,
enumerator: Enumerator,
indenter: Indenter,
}
impl Renderer {
pub fn new() -> Self {
Self {
style: TreeStyle::default(),
enumerator: default_enumerator,
indenter: default_indenter,
}
}
pub fn style(mut self, style: TreeStyle) -> Self {
self.style = style;
self
}
pub fn enumerator(mut self, enumerator: Enumerator) -> Self {
self.enumerator = enumerator;
self
}
pub fn indenter(mut self, indenter: Indenter) -> Self {
self.indenter = indenter;
self
}
pub fn render(&self, node: &dyn Node, root: bool, prefix: &str) -> String {
if node.hidden() {
return String::new();
}
let mut strs = Vec::new();
let children = node.children();
let enumerator = node.get_enumerator().copied().unwrap_or(self.enumerator);
let indenter = node.get_indenter().copied().unwrap_or(self.indenter);
if !node.value().is_empty() && root {
strs.push(self.style.root.render(&node.value()));
}
let mut visible_nodes: Vec<Box<dyn Node>> = Vec::new();
for i in 0..children.length() {
if let Some(child) = children.at(i) {
if child.hidden() || child.value().is_empty() {
continue;
}
visible_nodes.push(child.clone_node());
}
}
let filtered_children = crate::children::NodeChildren::from_nodes(visible_nodes);
fn has_visible_line(node: &dyn Node) -> bool {
if node.hidden() {
return false;
}
if !node.value().is_empty() {
return true;
}
let ch = node.children();
for i in 0..ch.length() {
if let Some(n) = ch.at(i) {
if has_visible_line(n) {
return true;
}
}
}
false
}
let mut is_last_vec: Vec<bool> = vec![true; filtered_children.length()];
#[allow(clippy::needless_range_loop)]
for vi in 0..filtered_children.length() {
let mut last = true;
let mut seen = 0usize;
for i in 0..children.length() {
if let Some(ch) = children.at(i) {
if ch.hidden() {
continue;
}
if !ch.value().is_empty() {
if seen == vi {
for j in (i + 1)..children.length() {
if let Some(next) = children.at(j) {
if has_visible_line(next) {
last = false;
break;
}
}
}
break;
}
seen += 1;
}
}
}
is_last_vec[vi] = last;
}
let mut vis_map: Vec<usize> = Vec::new();
for i in 0..children.length() {
if let Some(ch) = children.at(i) {
if !ch.hidden() && !ch.value().is_empty() {
vis_map.push(i);
}
}
}
let vis_children = VisibleChildren {
base: &*children,
map: vis_map,
};
let is_branch = |s: &str| s == "├──" || s == "└──" || s == "╰──";
let mut max_enum_width = 0;
for i in 0..filtered_children.length() {
let user_pref = enumerator(&vis_children, i);
if !is_branch(&user_pref) {
let width = user_pref.width();
if width > max_enum_width {
max_enum_width = width;
}
}
}
let mut last_display_indent = String::new();
for i in 0..children.length() {
if let Some(child) = children.at(i) {
if child.hidden() {
continue;
}
let mut display_idx_opt: Option<usize> = None;
if !child.value().is_empty() {
let mut count = 0usize;
for j in 0..i {
if let Some(prev) = children.at(j) {
if !prev.hidden() && !prev.value().is_empty() {
count += 1;
}
}
}
display_idx_opt = Some(count);
}
let idx = display_idx_opt.unwrap_or(0);
let enum_style_func = node
.get_enumerator_style_func()
.copied()
.unwrap_or(self.style.enumerator_func);
let item_style_func = node
.get_item_style_func()
.copied()
.unwrap_or(self.style.item_func);
let enum_base = node
.get_enumerator_style()
.cloned()
.or_else(|| self.style.enumerator_base.clone());
let item_base = node
.get_item_style()
.cloned()
.or_else(|| self.style.item_base.clone());
let raw_indent = if let Some(di) = display_idx_opt {
indenter(&filtered_children, di)
} else {
last_display_indent.clone()
};
let indent = if raw_indent.trim().is_empty() {
if let Some(base) = &item_base {
base.render(&raw_indent)
} else {
raw_indent.clone()
}
} else {
if let Some(base) = &enum_base {
base.render(&raw_indent)
} else {
raw_indent.clone()
}
};
let user_pref = enumerator(&vis_children, idx);
let is_custom_enum = !is_branch(&user_pref);
let mut node_prefix = if !is_custom_enum {
let dc = DummyChildren { len: 2 };
if is_last_vec[idx] {
enumerator(&dc, 1)
} else {
enumerator(&dc, 0)
}
} else {
user_pref.clone()
};
if !is_custom_enum {
} else if max_enum_width > 0 {
let current_width = node_prefix.width();
let padding_needed = max_enum_width.saturating_sub(current_width);
if padding_needed > 0 {
node_prefix = format!("{}{}", " ".repeat(padding_needed), node_prefix);
}
}
if let Some(base) = &enum_base {
node_prefix = base.render(&node_prefix);
} else {
let enum_style_result = enum_style_func(&vis_children, idx);
let enum_lead = enum_style_result.render("");
if !enum_lead.is_empty() && !enum_lead.trim().is_empty() {
let default_styled = Style::new().padding_right(1).render(&node_prefix);
if !enum_lead.ends_with(' ') {
node_prefix = format!("{} {}", enum_lead, default_styled);
} else {
node_prefix = format!("{}{}", enum_lead, default_styled);
}
} else {
node_prefix = enum_style_result.render(&node_prefix);
}
}
let mut item = child.value();
if let Some(base) = &item_base {
item = base.render(&item);
} else {
let item_style_result = item_style_func(&vis_children, idx);
let item_lead = item_style_result.render("");
let is_padding_only = item_lead.chars().all(|c| c.is_whitespace());
if is_padding_only {
item = item_style_result.render(&item);
} else {
if !item_lead.ends_with(' ') {
item = format!("{} {}", item_lead, item);
} else {
item = format!("{}{}", item_lead, item);
}
}
}
let mut multiline_prefix = prefix.to_string();
let item_height = height(&item);
let mut node_prefix_height = height(&node_prefix);
while item_height > node_prefix_height {
let extension_indent = indent.clone();
node_prefix = join_vertical(LEFT, &[&node_prefix, &extension_indent]);
node_prefix_height = height(&node_prefix);
}
let mut multiline_prefix_height = height(&multiline_prefix);
while node_prefix_height > multiline_prefix_height {
multiline_prefix = join_vertical(LEFT, &[&multiline_prefix, prefix]);
multiline_prefix_height = height(&multiline_prefix);
}
if !child.value().is_empty() {
let styled_multiline_prefix = if !multiline_prefix.trim().is_empty() {
if let Some(base) = &enum_base {
if base.get_background().is_some() {
base.render(&multiline_prefix)
} else {
multiline_prefix.clone()
}
} else if let Some(base) = &item_base {
if base.get_background().is_some() {
base.render(&multiline_prefix)
} else {
multiline_prefix.clone()
}
} else {
multiline_prefix.clone()
}
} else {
multiline_prefix.clone()
};
let line =
join_horizontal(TOP, &[&styled_multiline_prefix, &node_prefix, &item]);
strs.push(line);
last_display_indent = raw_indent.clone();
}
if child.children().length() > 0 {
let styled_indent = indent.clone();
let dummy_children = crate::children::NodeChildren::new();
let parent_indent_sample = indenter(&dummy_children, 0);
#[allow(unused_variables)]
let is_parent_list_indenter =
parent_indent_sample.trim() == "" && parent_indent_sample.len() == 2;
let child_prefix = if let Some(child_indenter) = child.get_indenter() {
let child_indent_sample = child_indenter(&dummy_children, 0);
#[allow(unused_variables)]
let is_child_tree_indenter =
child_indent_sample.contains('│') || child_indent_sample.len() == 4;
format!("{}{}", prefix, styled_indent)
} else {
format!("{}{}", prefix, styled_indent)
};
let has_style_overrides = child.get_enumerator_style().is_some()
|| child.get_item_style().is_some()
|| child.get_enumerator_style_func().is_some()
|| child.get_item_style_func().is_some();
let child_uses_tree_indenter =
if let Some(child_indenter) = child.get_indenter() {
let dummy = crate::children::NodeChildren::new();
let sample = child_indenter(&dummy, 0);
sample.contains('│') || sample.len() == 4
} else {
false
};
let mut child_renderer = if child_uses_tree_indenter {
Renderer::new()
} else if has_style_overrides {
Renderer::new()
} else {
Renderer::new()
.enumerator(self.enumerator)
.indenter(self.indenter)
};
if let Some(e) = child.get_enumerator() {
child_renderer = child_renderer.enumerator(*e);
}
if let Some(i) = child.get_indenter() {
child_renderer = child_renderer.indenter(*i);
}
let style = TreeStyle {
enumerator_func: child
.get_enumerator_style_func()
.copied()
.unwrap_or(|_, _| Style::new().padding_right(1)),
item_func: child
.get_item_style_func()
.copied()
.unwrap_or(|_, _| Style::new()),
root: Style::default(),
enumerator_base: child
.get_enumerator_style()
.cloned()
.or_else(|| self.style.enumerator_base.clone()),
item_base: child
.get_item_style()
.cloned()
.or_else(|| self.style.item_base.clone()),
};
child_renderer = child_renderer.style(style);
let mut child_output = child_renderer.render(child, false, &child_prefix);
if child.value().is_empty() {
let mut future_exists = false;
for j in (i + 1)..children.length() {
if let Some(next) = children.at(j) {
if next.value().is_empty() && has_visible_line(next) {
future_exists = true;
break;
}
}
}
if future_exists {
let dc = DummyChildren { len: 2 };
let last_branch = enumerator(&dc, 1);
let mid_branch = enumerator(&dc, 0);
let look_for = format!("{}{}", child_prefix, last_branch);
if let Some(pos) = child_output.rfind(&look_for) {
let line_start =
child_output[..pos].rfind('\n').map(|p| p + 1).unwrap_or(0);
if line_start == pos {
child_output.replace_range(
pos..pos + look_for.len(),
&format!("{}{}", child_prefix, mid_branch),
);
}
}
}
}
if !child_output.is_empty() {
strs.push(child_output);
}
}
}
}
strs.join("\n")
}
}
impl Default for Renderer {
fn default() -> Self {
Self::new()
}
}
pub fn new_renderer() -> Renderer {
Renderer::new()
}