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);
}
}
}