using System.Collections.Generic; using System.Linq; using Spectre.Console.Rendering; namespace Spectre.Console { /// /// Representation of non-circular tree data. /// Each node added to the tree may only be present in it a single time, in order to facilitate cycle detection. /// public sealed class Tree : Renderable, IHasTreeNodes { private readonly TreeNode _root; /// /// Gets or sets the tree style. /// public Style? Style { get; set; } /// /// Gets or sets the tree guide lines. /// public TreeGuide Guide { get; set; } = TreeGuide.Line; /// /// Gets the tree's child nodes. /// public List Nodes => _root.Nodes; /// /// Gets or sets a value indicating whether or not the tree is expanded or not. /// public bool Expanded { get; set; } = true; /// /// Initializes a new instance of the class. /// /// The tree label. public Tree(IRenderable renderable) { _root = new TreeNode(renderable); } /// /// Initializes a new instance of the class. /// /// The tree label. public Tree(string label) { _root = new TreeNode(new Markup(label)); } /// protected override IEnumerable Render(RenderContext context, int maxWidth) { var result = new List(); var visitedNodes = new HashSet(); var stack = new Stack>(); stack.Push(new Queue(new[] { _root })); var levels = new List(); levels.Add(GetGuide(context, TreeGuidePart.Continue)); while (stack.Count > 0) { var stackNode = stack.Pop(); if (stackNode.Count == 0) { levels.RemoveLast(); if (levels.Count > 0) { levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork)); } continue; } var isLastChild = stackNode.Count == 1; var current = stackNode.Dequeue(); if (!visitedNodes.Add(current)) { throw new CircularTreeException("Cycle detected in tree - unable to render."); } stack.Push(stackNode); if (isLastChild) { levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End)); } var prefix = levels.Skip(1).ToList(); var renderableLines = Segment.SplitLines(current.Renderable.Render(context, maxWidth - Segment.CellCount(prefix))); foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate()) { if (prefix.Count > 0) { result.AddRange(prefix.ToList()); } result.AddRange(line); result.Add(Segment.LineBreak); if (isFirstLine && prefix.Count > 0) { var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue; prefix.AddOrReplaceLast(GetGuide(context, part)); } } if (current.Expanded && current.Nodes.Count > 0) { levels.AddOrReplaceLast(GetGuide(context, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue)); levels.Add(GetGuide(context, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork)); stack.Push(new Queue(current.Nodes)); } } return result; } private Segment GetGuide(RenderContext context, TreeGuidePart part) { var guide = Guide.GetSafeTreeGuide(safe: !context.Unicode); return new Segment(guide.GetPart(part), Style ?? Style.Plain); } } }