lipgloss_tree/
renderer.rs

1//! Tree rendering engine for lipgloss-tree.
2//!
3//! This module provides the core rendering functionality for tree structures,
4//! handling complex styling, alignment, and multiline content rendering.
5//! It supports both built-in tree glyphs (├──, └──, etc.) and custom
6//! enumerators with proper alignment and styling inheritance.
7
8use crate::children::Children;
9use crate::{default_enumerator, default_indenter, Enumerator, Indenter, Node, StyleFunc};
10use lipgloss::{height, join_horizontal, join_vertical, Style, LEFT, TOP};
11use unicode_width::UnicodeWidthStr;
12
13/// Minimal Children implementation used to synthesize enumerator glyphs with controlled length/index.
14///
15/// This is used internally by the renderer to generate the correct branch characters
16/// (├── vs └──) based on whether a node is the last in its sequence.
17struct DummyChildren {
18    /// The virtual length of this children collection
19    len: usize,
20}
21impl Children for DummyChildren {
22    fn at(&self, _index: usize) -> Option<&dyn Node> {
23        None
24    }
25    fn length(&self) -> usize {
26        self.len
27    }
28}
29
30/// A filtered view of children that exposes only visible, non-empty nodes via an index map.
31///
32/// This wrapper allows the renderer to work with only the nodes that will actually
33/// produce visible output, while maintaining the correct indices for style functions.
34struct VisibleChildren<'a> {
35    /// Reference to the underlying children collection
36    base: &'a dyn Children,
37    /// Mapping from visible index to actual index in the base collection
38    map: Vec<usize>,
39}
40impl<'a> Children for VisibleChildren<'a> {
41    fn at(&self, index: usize) -> Option<&dyn Node> {
42        self.map.get(index).and_then(|&i| self.base.at(i))
43    }
44    fn length(&self) -> usize {
45        self.map.len()
46    }
47}
48
49/// Styling configuration for tree rendering.
50///
51/// `TreeStyle` controls how different parts of the tree are styled, including
52/// enumerators (branch characters like ├──), items (node content), and the root.
53/// It supports both base styles (applied unconditionally) and function styles
54/// (applied per-child based on index and context).
55///
56/// # Examples
57///
58/// ```rust
59/// use lipgloss::Style;
60/// use lipgloss_tree::renderer::TreeStyle;
61///
62/// let style = TreeStyle {
63///     enumerator_func: |_, _| Style::new().foreground("blue"),
64///     item_func: |_, _| Style::new().bold(true),
65///     enumerator_base: Some(Style::new().padding_right(1)),
66///     item_base: None,
67///     root: Style::new().bold(true),
68/// };
69/// ```
70#[derive(Debug, Clone)]
71pub struct TreeStyle {
72    /// Function style applied to enumerators (branch characters) based on child index
73    pub enumerator_func: StyleFunc,
74    /// Function style applied to item content based on child index  
75    pub item_func: StyleFunc,
76    /// Base style applied unconditionally to all enumerators before function styles
77    pub enumerator_base: Option<Style>,
78    /// Base style applied unconditionally to all items before function styles
79    pub item_base: Option<Style>,
80    /// Style applied to the root node
81    pub root: Style,
82}
83
84impl Default for TreeStyle {
85    /// Creates a default `TreeStyle` with Go lipgloss-compatible behavior.
86    ///
87    /// The default includes `padding_right(1)` on enumerators to match Go's
88    /// behavior of printing a space after tree branch prefixes. Tests that
89    /// need different behavior can override with no-op functions.
90    fn default() -> Self {
91        Self {
92            // By default, Go prints a space after the prefix. Model this
93            // with padding_right(1). Tests that set nil funcs expect no space
94            // and will override with a no-op function.
95            enumerator_func: |_children, _i| Style::new().padding_right(1),
96            item_func: |_children, _i| Style::new(),
97            enumerator_base: None,
98            item_base: None,
99            root: Style::new(),
100        }
101    }
102}
103
104/// The main tree rendering engine.
105///
106/// `Renderer` is responsible for converting tree node structures into styled
107/// text output. It handles complex features like:
108/// - Multi-line content with proper indentation
109/// - Custom enumerators and indenters  
110/// - Style inheritance and override resolution
111/// - Alignment of custom enumerators
112/// - Branch continuation across container nodes
113///
114/// # Examples
115///
116/// ```rust
117/// use lipgloss_tree::{Renderer, Children};
118/// use lipgloss_tree::renderer::TreeStyle;
119/// use lipgloss::Style;
120///
121/// let renderer = Renderer::new()
122///     .style(TreeStyle::default())
123///     .enumerator(|children, i| {
124///         if i == children.length() - 1 { "└──".to_string() }
125///         else { "├──".to_string() }
126///     });
127/// ```
128#[derive(Clone)]
129pub struct Renderer {
130    /// Styling configuration for this renderer
131    style: TreeStyle,
132    /// Function to generate enumerator strings (branch characters)
133    enumerator: Enumerator,
134    /// Function to generate indentation strings for nested content
135    indenter: Indenter,
136}
137
138impl Renderer {
139    /// Creates a new renderer with default configuration.
140    ///
141    /// The default renderer uses:
142    /// - Standard box-drawing characters for branches (├──, └──)
143    /// - Standard indentation (│   for continuing, spaces for last)
144    /// - Default styling with padding after enumerators
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use lipgloss_tree::Renderer;
150    ///
151    /// let renderer = Renderer::new();
152    /// ```
153    pub fn new() -> Self {
154        Self {
155            style: TreeStyle::default(),
156            enumerator: default_enumerator,
157            indenter: default_indenter,
158        }
159    }
160
161    /// Sets the styling configuration for this renderer.
162    ///
163    /// This replaces the entire `TreeStyle`, affecting how enumerators,
164    /// items, and the root are styled.
165    ///
166    /// # Arguments
167    ///
168    /// * `style` - The new `TreeStyle` configuration to use
169    ///
170    /// # Examples
171    ///
172    /// ```rust
173    /// use lipgloss_tree::Renderer;
174    /// use lipgloss_tree::renderer::TreeStyle;
175    /// use lipgloss::Style;
176    ///
177    /// let custom_style = TreeStyle {
178    ///     root: Style::new().bold(true),
179    ///     ..TreeStyle::default()
180    /// };
181    ///
182    /// let renderer = Renderer::new().style(custom_style);
183    /// ```
184    pub fn style(mut self, style: TreeStyle) -> Self {
185        self.style = style;
186        self
187    }
188
189    /// Sets the enumerator function for this renderer.
190    ///
191    /// The enumerator function generates the branch characters (like ├──, └──)
192    /// based on the child's position in its parent's children collection.
193    ///
194    /// # Arguments
195    ///
196    /// * `enumerator` - Function that takes `(children, index)` and returns a string
197    ///
198    /// # Examples
199    ///
200    /// ```rust
201    /// use lipgloss_tree::{Renderer, Children};
202    ///
203    /// let renderer = Renderer::new()
204    ///     .enumerator(|children, i| {
205    ///         if i == children.length() - 1 {
206    ///             "╰──".to_string()  // Rounded last branch
207    ///         } else {
208    ///             "├──".to_string()  // Standard continuing branch
209    ///         }
210    ///     });
211    /// ```
212    pub fn enumerator(mut self, enumerator: Enumerator) -> Self {
213        self.enumerator = enumerator;
214        self
215    }
216
217    /// Sets the indenter function for this renderer.
218    ///
219    /// The indenter function generates indentation strings for child content
220    /// that appears below parent nodes, providing the visual connection
221    /// between parent and nested children.
222    ///
223    /// # Arguments
224    ///
225    /// * `indenter` - Function that takes `(children, index)` and returns indentation string
226    ///
227    /// # Examples
228    ///
229    /// ```rust
230    /// use lipgloss_tree::{Renderer, Children};
231    ///
232    /// let renderer = Renderer::new()
233    ///     .indenter(|children, i| {
234    ///         if i == children.length() - 1 {
235    ///             "    ".to_string()  // Spaces for last child
236    ///         } else {
237    ///             "│   ".to_string()  // Vertical line for continuing
238    ///         }
239    ///     });
240    /// ```
241    pub fn indenter(mut self, indenter: Indenter) -> Self {
242        self.indenter = indenter;
243        self
244    }
245
246    /// Renders a tree node and its children to a formatted string.
247    ///
248    /// This is the main rendering method that converts a tree structure into
249    /// styled text output. It handles complex scenarios like multi-line content,
250    /// style inheritance, custom enumerators, and proper indentation.
251    ///
252    /// # Arguments
253    ///
254    /// * `node` - The tree node to render
255    /// * `root` - Whether this is the root node (affects root style application)
256    /// * `prefix` - String prefix to prepend to each line (for nested rendering)
257    ///
258    /// # Returns
259    ///
260    /// A formatted string representation of the tree with proper styling and indentation
261    ///
262    /// # Examples
263    ///
264    /// ```rust
265    /// use lipgloss_tree::{Renderer, Tree};
266    ///
267    /// let tree = Tree::new()
268    ///     .root("My Tree")
269    ///     .child(vec!["Item 1".into(), "Item 2".into()]);
270    ///
271    /// let renderer = Renderer::new();
272    /// let output = renderer.render(&tree, true, "");
273    /// println!("{}", output);
274    /// ```
275    pub fn render(&self, node: &dyn Node, root: bool, prefix: &str) -> String {
276        if node.hidden() {
277            return String::new();
278        }
279
280        // Debug: uncomment for debugging
281        // eprintln!("RENDER_START: root={}, prefix='{}', prefix_len={}", root, prefix.replace('\n', "\\n"), prefix.len());
282
283        let mut strs = Vec::new();
284        let children = node.children();
285        // Prefer per-node overrides for enumerator/indenter when present, otherwise use renderer config
286        let enumerator = node.get_enumerator().copied().unwrap_or(self.enumerator);
287        let indenter = node.get_indenter().copied().unwrap_or(self.indenter);
288
289        // Print the root node name if it's not empty
290        if !node.value().is_empty() && root {
291            strs.push(self.style.root.render(&node.value()));
292        }
293
294        // Build a filtered view of direct children that will render a line (non-hidden, non-empty)
295        let mut visible_nodes: Vec<Box<dyn Node>> = Vec::new();
296        for i in 0..children.length() {
297            if let Some(child) = children.at(i) {
298                if child.hidden() || child.value().is_empty() {
299                    continue;
300                }
301                visible_nodes.push(child.clone_node());
302            }
303        }
304        let filtered_children = crate::children::NodeChildren::from_nodes(visible_nodes);
305
306        // Helper: does this node or any of its descendants render a visible line?
307        fn has_visible_line(node: &dyn Node) -> bool {
308            if node.hidden() {
309                return false;
310            }
311            if !node.value().is_empty() {
312                return true;
313            }
314            let ch = node.children();
315            for i in 0..ch.length() {
316                if let Some(n) = ch.at(i) {
317                    if has_visible_line(n) {
318                        return true;
319                    }
320                }
321            }
322            false
323        }
324
325        // Precompute which visible direct child is last in the overall visual sequence.
326        // A visible child is last if there is no later sibling (direct) whose subtree would
327        // render any visible line.
328        let mut is_last_vec: Vec<bool> = vec![true; filtered_children.length()];
329        #[allow(clippy::needless_range_loop)]
330        for vi in 0..filtered_children.length() {
331            let mut last = true;
332            // locate this visible child among all direct children
333            let mut seen = 0usize;
334            for i in 0..children.length() {
335                if let Some(ch) = children.at(i) {
336                    if ch.hidden() {
337                        continue;
338                    }
339                    if !ch.value().is_empty() {
340                        if seen == vi {
341                            // check later siblings
342                            for j in (i + 1)..children.length() {
343                                if let Some(next) = children.at(j) {
344                                    if has_visible_line(next) {
345                                        last = false;
346                                        break;
347                                    }
348                                }
349                            }
350                            break;
351                        }
352                        seen += 1;
353                    }
354                }
355            }
356            is_last_vec[vi] = last;
357        }
358
359        // Prepare a visible-children view for style functions
360        let mut vis_map: Vec<usize> = Vec::new();
361        for i in 0..children.length() {
362            if let Some(ch) = children.at(i) {
363                if !ch.hidden() && !ch.value().is_empty() {
364                    vis_map.push(i);
365                }
366            }
367        }
368        let vis_children = VisibleChildren {
369            base: &*children,
370            map: vis_map,
371        };
372
373        // Helper to detect built-in branch glyphs
374        let is_branch = |s: &str| s == "├──" || s == "└──" || s == "╰──";
375
376        // Calculate alignment padding for custom enumerators (not built-in branch glyphs)
377        let mut max_enum_width = 0;
378        for i in 0..filtered_children.length() {
379            let user_pref = enumerator(&vis_children, i);
380            if !is_branch(&user_pref) {
381                // Only consider custom enumerators for alignment
382                let width = user_pref.width();
383                if width > max_enum_width {
384                    max_enum_width = width;
385                }
386            }
387        }
388
389        // Render children
390        let mut last_display_indent = String::new();
391        for i in 0..children.length() {
392            if let Some(child) = children.at(i) {
393                if child.hidden() {
394                    continue;
395                }
396
397                // Determine display index for visible children by counting prior visible siblings
398                let mut display_idx_opt: Option<usize> = None;
399                if !child.value().is_empty() {
400                    let mut count = 0usize;
401                    for j in 0..i {
402                        if let Some(prev) = children.at(j) {
403                            if !prev.hidden() && !prev.value().is_empty() {
404                                count += 1;
405                            }
406                        }
407                    }
408                    display_idx_opt = Some(count);
409                }
410                let idx = display_idx_opt.unwrap_or(0);
411                // Build effective styles for this node, respecting node overrides first
412                let enum_style_func = node
413                    .get_enumerator_style_func()
414                    .copied()
415                    .unwrap_or(self.style.enumerator_func);
416                let item_style_func = node
417                    .get_item_style_func()
418                    .copied()
419                    .unwrap_or(self.style.item_func);
420
421                let enum_base = node
422                    .get_enumerator_style()
423                    .cloned()
424                    .or_else(|| self.style.enumerator_base.clone());
425                let item_base = node
426                    .get_item_style()
427                    .cloned()
428                    .or_else(|| self.style.item_base.clone());
429
430                // Compute indent: for visible children use indenter(filtered, display_idx);
431                // for container (empty value) nodes, reuse the last visible indent so nested
432                // content attaches under the previous item.
433                let raw_indent = if let Some(di) = display_idx_opt {
434                    indenter(&filtered_children, di)
435                } else {
436                    last_display_indent.clone()
437                };
438                // Apply styling to indent based on the type of indenter
439                let indent = if raw_indent.trim().is_empty() {
440                    // Standard whitespace indenter - apply item_base style if present (for padding)
441                    if let Some(base) = &item_base {
442                        base.render(&raw_indent)
443                    } else {
444                        raw_indent.clone()
445                    }
446                } else {
447                    // Custom indenter (like "->") - apply enum_base style if present (for colors/styling)
448                    if let Some(base) = &enum_base {
449                        base.render(&raw_indent)
450                    } else {
451                        raw_indent.clone()
452                    }
453                };
454
455                // Compute enumerator only for visible children
456                // Base branch according to position in overall visual sequence
457                let user_pref = enumerator(&vis_children, idx);
458                let is_custom_enum = !is_branch(&user_pref);
459                let mut node_prefix = if !is_custom_enum {
460                    let dc = DummyChildren { len: 2 };
461                    if is_last_vec[idx] {
462                        enumerator(&dc, 1)
463                    } else {
464                        enumerator(&dc, 0)
465                    }
466                } else {
467                    user_pref.clone()
468                };
469
470                // Apply alignment padding for custom enumerators
471                if !is_custom_enum {
472                    // Built-in branch glyph - no alignment needed
473                } else if max_enum_width > 0 {
474                    // Custom enumerator - apply alignment padding
475                    let current_width = node_prefix.width();
476                    let padding_needed = max_enum_width.saturating_sub(current_width);
477                    if padding_needed > 0 {
478                        node_prefix = format!("{}{}", " ".repeat(padding_needed), node_prefix);
479                    }
480                }
481
482                // CRITICAL: Apply either base style OR function style, NEVER both
483                // This prevents double-padding issues where both base and function styles add spacing
484                //
485                // PADDING_RIGHT BEHAVIOR:
486                // - TreeStyle::default() sets enumerator_func to add padding_right(1)
487                // - If enumerator_style (base) is set, it REPLACES the function entirely
488                // - The base style can include its own padding_right(1)
489                // - Go behavior: EnumeratorStyle() method replaces the default function
490                //
491                // SPACING CALCULATION:
492                // - Tree symbols: "├──", "└──" are 3 chars wide
493                // - padding_right(1) adds 1 space → "├── " (4 chars total)
494                // - This aligns with default_indenter: "│   " (4 chars) and "    " (4 spaces)
495                if let Some(base) = &enum_base {
496                    // Base style set via .enumerator_style() - use ONLY this style
497                    // Example: Style::new().foreground(color).padding_right(1)
498                    node_prefix = base.render(&node_prefix);
499                } else {
500                    // No base style - use the function style (default or custom)
501                    let enum_style_result = enum_style_func(&vis_children, idx);
502                    let enum_lead = enum_style_result.render("");
503
504                    // Check if this is a set_string style vs padding-only style
505                    if !enum_lead.is_empty() && !enum_lead.trim().is_empty() {
506                        // Set_string style with actual content (e.g., "+" prefix)
507                        // Apply default padding to tree structure, then prepend the content
508                        let default_styled = Style::new().padding_right(1).render(&node_prefix);
509                        if !enum_lead.ends_with(' ') {
510                            node_prefix = format!("{} {}", enum_lead, default_styled);
511                        } else {
512                            node_prefix = format!("{}{}", enum_lead, default_styled);
513                        }
514                    } else {
515                        // Padding-only style or no additional content
516                        // Apply style function directly to tree structure
517                        node_prefix = enum_style_result.render(&node_prefix);
518                    }
519                }
520                // Note: Alignment padding disabled - Go doesn't align prefixes to same width
521                // Debug: uncomment for debugging
522                // eprintln!("RENDER: idx={}, prefix='{}', width={}, max_len={}, final='{}'", idx, node_prefix.replace('\n', "\\n"), prefix_width, max_len, node_prefix.replace('\n', "\\n"));
523                // Apply item styling: base style OR function style, not both
524                let mut item = child.value();
525                if let Some(base) = &item_base {
526                    item = base.render(&item);
527                } else {
528                    // Only apply function style if no base style is set
529                    let item_style_result = item_style_func(&vis_children, idx);
530                    let item_lead = item_style_result.render("");
531
532                    // Check if this is a true set_string style vs padding-only style
533                    // Padding-only styles render to whitespace-only strings (spaces, tabs, newlines)
534                    // Set_string styles have non-whitespace content
535                    let is_padding_only = item_lead.chars().all(|c| c.is_whitespace());
536
537                    if is_padding_only {
538                        // Apply the style function to the item directly (padding-only or no style)
539                        item = item_style_result.render(&item);
540                    } else {
541                        // This is a style with a string set via set_string (like Go's SetString)
542                        if !item_lead.ends_with(' ') {
543                            item = format!("{} {}", item_lead, item);
544                        } else {
545                            item = format!("{}{}", item_lead, item);
546                        }
547                    }
548                }
549                let mut multiline_prefix = prefix.to_string();
550
551                // Handle multiline prefixes and items
552                let item_height = height(&item);
553                let mut node_prefix_height = height(&node_prefix);
554
555                // Extend node prefix if item is taller
556                while item_height > node_prefix_height {
557                    // Use raw indent for multiline extension - no styling needed
558                    let extension_indent = indent.clone();
559                    node_prefix = join_vertical(LEFT, &[&node_prefix, &extension_indent]);
560                    node_prefix_height = height(&node_prefix);
561                }
562
563                // Extend multiline prefix if node prefix is taller
564                let mut multiline_prefix_height = height(&multiline_prefix);
565                while node_prefix_height > multiline_prefix_height {
566                    multiline_prefix = join_vertical(LEFT, &[&multiline_prefix, prefix]);
567                    multiline_prefix_height = height(&multiline_prefix);
568                }
569
570                // Only emit a line if the child has a non-empty value; empty-value nodes are containers for sublists
571                if !child.value().is_empty() {
572                    // FINAL LINE ASSEMBLY:
573                    // multiline_prefix: parent indentation (e.g., 10 spaces from nested list level)
574                    // node_prefix: tree symbol with styling (e.g., "[color]├──[reset] " with padding_right)
575                    // item: content with any item styling applied
576                    //
577                    // BACKGROUND COLOR CONSISTENCY:
578                    // When tree components have background colors, we need to ensure the multiline_prefix
579                    // also gets the same background color to avoid black rectangular gaps.
580
581                    let styled_multiline_prefix = if !multiline_prefix.trim().is_empty() {
582                        // If there is indentation content, check if we should apply background styling
583                        // Priority: enum_base > item_base (prefer the enumerator background)
584                        if let Some(base) = &enum_base {
585                            // If enumerator has background, apply it to prefix indentation
586                            if base.get_background().is_some() {
587                                base.render(&multiline_prefix)
588                            } else {
589                                multiline_prefix.clone()
590                            }
591                        } else if let Some(base) = &item_base {
592                            // If item has background, apply it to prefix indentation
593                            if base.get_background().is_some() {
594                                base.render(&multiline_prefix)
595                            } else {
596                                multiline_prefix.clone()
597                            }
598                        } else {
599                            multiline_prefix.clone()
600                        }
601                    } else {
602                        multiline_prefix.clone()
603                    };
604
605                    // DEBUG: Uncomment to debug spacing issues
606                    // eprintln!("DEBUG: multiline_prefix='{}', node_prefix='{}', item='{}'",
607                    //           styled_multiline_prefix.replace(' ', "·"),
608                    //           node_prefix.replace(' ', "·"),
609                    //           item.replace(' ', "·"));
610
611                    let line =
612                        join_horizontal(TOP, &[&styled_multiline_prefix, &node_prefix, &item]);
613                    strs.push(line);
614                    // Remember raw indent for subsequent container nodes (before styling)
615                    last_display_indent = raw_indent.clone();
616                }
617
618                // Recursively render children
619                if child.children().length() > 0 {
620                    // Even if the child has an empty value (container), we still need to
621                    // indent its children so they appear nested under the current item.
622                    // Use styled indent with enum styling applied to indenter characters
623                    // This ensures custom indenters (like "->") get the same styling as enumerators
624                    let styled_indent = indent.clone();
625
626                    // SMART INDENTATION LOGIC:
627                    // Trees nested in lists should not inherit the list's indenter to avoid double indentation.
628                    let dummy_children = crate::children::NodeChildren::new();
629                    let parent_indent_sample = indenter(&dummy_children, 0);
630                    #[allow(unused_variables)]
631                    let is_parent_list_indenter =
632                        parent_indent_sample.trim() == "" && parent_indent_sample.len() == 2;
633
634                    let child_prefix = if let Some(child_indenter) = child.get_indenter() {
635                        let child_indent_sample = child_indenter(&dummy_children, 0);
636                        #[allow(unused_variables)]
637                        let is_child_tree_indenter =
638                            child_indent_sample.contains('│') || child_indent_sample.len() == 4;
639
640                        // DEBUG: uncomment for debugging tree indentation
641                        // if child.get_enumerator_style().is_some() {
642                        //     eprintln!("DEBUG TREE: parent_indent='{}', child_indent='{}', is_parent_list={}, is_child_tree={}, prefix_len={}",
643                        //         parent_indent_sample.replace(' ', "·"), child_indent_sample.replace(' ', "·"), is_parent_list_indenter, is_child_tree_indenter, prefix.len());
644                        // }
645
646                        // Always provide proper nesting prefix - the issue is tree's internal indentation, not prefix
647                        format!("{}{}", prefix, styled_indent)
648                    } else {
649                        // Child has no specific indenter - inherit parent's indentation
650                        format!("{}{}", prefix, styled_indent)
651                    };
652
653                    // Detect if child has style overrides (indicating it's a styled tree vs plain container)
654                    let has_style_overrides = child.get_enumerator_style().is_some()
655                        || child.get_item_style().is_some()
656                        || child.get_enumerator_style_func().is_some()
657                        || child.get_item_style_func().is_some();
658
659                    // Special case: if this child is a tree with its own indenter, use fresh renderer
660                    // to prevent list indenter from affecting tree's internal rendering
661                    let child_uses_tree_indenter =
662                        if let Some(child_indenter) = child.get_indenter() {
663                            let dummy = crate::children::NodeChildren::new();
664                            let sample = child_indenter(&dummy, 0);
665                            sample.contains('│') || sample.len() == 4
666                        } else {
667                            false
668                        };
669
670                    let mut child_renderer = if child_uses_tree_indenter {
671                        // Tree child: use fresh renderer to avoid inheriting list behavior
672                        Renderer::new()
673                    } else if has_style_overrides {
674                        // Child has style overrides: use tree defaults for behavior but child styles
675                        Renderer::new()
676                    } else {
677                        // Child has no overrides: inherit parent's behavior
678                        Renderer::new()
679                            .enumerator(self.enumerator)
680                            .indenter(self.indenter)
681                    };
682
683                    // Apply any explicit functional overrides from the child node itself
684                    if let Some(e) = child.get_enumerator() {
685                        child_renderer = child_renderer.enumerator(*e);
686                    }
687                    if let Some(i) = child.get_indenter() {
688                        child_renderer = child_renderer.indenter(*i);
689                    }
690
691                    // For style functions, use tree defaults unless child has specific overrides
692                    // Children should inherit parent styles when they don't have their own
693                    let style = TreeStyle {
694                        enumerator_func: child
695                            .get_enumerator_style_func()
696                            .copied()
697                            .unwrap_or(|_, _| Style::new().padding_right(1)),
698                        item_func: child
699                            .get_item_style_func()
700                            .copied()
701                            .unwrap_or(|_, _| Style::new()),
702                        root: Style::default(),
703                        // Inherit parent's base styles if child doesn't have overrides
704                        enumerator_base: child
705                            .get_enumerator_style()
706                            .cloned()
707                            .or_else(|| self.style.enumerator_base.clone()),
708                        item_base: child
709                            .get_item_style()
710                            .cloned()
711                            .or_else(|| self.style.item_base.clone()),
712                    };
713                    child_renderer = child_renderer.style(style);
714
715                    let mut child_output = child_renderer.render(child, false, &child_prefix);
716                    // If this child is an unnamed container and there are later siblings that
717                    // will render visible lines, ensure the container's last visible branch uses
718                    // the mid-branch glyph to visually continue the vertical line across
719                    // containers (e.g., the 5th Qux before subsequent Quux entries).
720                    if child.value().is_empty() {
721                        let mut future_exists = false;
722                        for j in (i + 1)..children.length() {
723                            if let Some(next) = children.at(j) {
724                                // Only consider later unnamed containers, which will visually
725                                // continue under the same prior item indent.
726                                if next.value().is_empty() && has_visible_line(next) {
727                                    future_exists = true;
728                                    break;
729                                }
730                            }
731                        }
732                        if future_exists {
733                            // Replace the deepest last-branch at this level with a mid-branch.
734                            let dc = DummyChildren { len: 2 };
735                            let last_branch = enumerator(&dc, 1);
736                            let mid_branch = enumerator(&dc, 0);
737                            let look_for = format!("{}{}", child_prefix, last_branch);
738                            if let Some(pos) = child_output.rfind(&look_for) {
739                                // Ensure we are at line start; find preceding newline or start
740                                let line_start =
741                                    child_output[..pos].rfind('\n').map(|p| p + 1).unwrap_or(0);
742                                if line_start == pos {
743                                    child_output.replace_range(
744                                        pos..pos + look_for.len(),
745                                        &format!("{}{}", child_prefix, mid_branch),
746                                    );
747                                }
748                            }
749                        }
750                    }
751                    if !child_output.is_empty() {
752                        strs.push(child_output);
753                    }
754                }
755            }
756        }
757
758        strs.join("\n")
759    }
760}
761
762impl Default for Renderer {
763    fn default() -> Self {
764        Self::new()
765    }
766}
767
768/// Creates a new renderer with default configuration.
769///
770/// This is a convenience function equivalent to `Renderer::new()`.
771/// Provided for API compatibility with other lipgloss libraries.
772///
773/// # Returns
774///
775/// A new `Renderer` instance with default settings
776///
777/// # Examples
778///
779/// ```rust
780/// use lipgloss_tree::renderer::new_renderer;
781///
782/// let renderer = new_renderer();
783/// ```
784pub fn new_renderer() -> Renderer {
785    Renderer::new()
786}