mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
Add support for multiple tree roots
This commit is contained in:
parent
4bfb24bfcb
commit
1aa958ced3
@ -0,0 +1,43 @@
|
|||||||
|
├── Root node
|
||||||
|
│ ├── child1
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 0
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 1
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 2
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 3
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 4
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 5
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 6
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 7
|
||||||
|
│ │ ├── multiple
|
||||||
|
│ │ │ line 8
|
||||||
|
│ │ └── multiple
|
||||||
|
│ │ line 9
|
||||||
|
│ ├── child2
|
||||||
|
│ │ └── child2Child
|
||||||
|
│ │ └── Child 2 child
|
||||||
|
│ │ child
|
||||||
|
│ └── child3
|
||||||
|
│ └── single leaf
|
||||||
|
│ multiline
|
||||||
|
│ └── 2020 January
|
||||||
|
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
|
||||||
|
│ │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
|
||||||
|
│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
|
||||||
|
│ │ │ │ │ 1 │ 2 │ 3 │ 4 │
|
||||||
|
│ │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
|
||||||
|
│ │ 12 │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │
|
||||||
|
│ │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │
|
||||||
|
│ │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │
|
||||||
|
│ │ │ │ │ │ │ │ │
|
||||||
|
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┘
|
||||||
|
└── child2Child
|
||||||
|
└── Child 2 child
|
||||||
|
child
|
@ -1,9 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Shouldly;
|
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
using Spectre.Console.Testing;
|
using Spectre.Console.Testing;
|
||||||
using VerifyXunit;
|
using VerifyXunit;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -14,77 +11,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
public class TreeTests
|
public class TreeTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Measure_Tree_Correctly()
|
public Task Should_Render_Tree_With_Single_Root_Correctly()
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var nestedChildren =
|
|
||||||
Enumerable.Range(0, 10)
|
|
||||||
.Select(x => new TreeNode(new Text($"multiple \n line {x}")));
|
|
||||||
var child3 = new TreeNode(new Text("child3"));
|
|
||||||
child3.AddChild(new TreeNode(new Text("single leaf\n multiline")));
|
|
||||||
var children = new List<TreeNode>
|
|
||||||
{
|
|
||||||
new(new Text("child1"), nestedChildren), new(new Text("child2")), child3,
|
|
||||||
};
|
|
||||||
var root = new TreeNode(new Text("Root node"), children);
|
|
||||||
var tree = new Tree(root);
|
|
||||||
|
|
||||||
// When
|
|
||||||
var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
measurement.Min.ShouldBe(17);
|
|
||||||
measurement.Max.ShouldBe(19);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Measure_Tree_Correctly_With_Regard_To_Max_Width()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var root = new TreeNode(new Text("Root node"));
|
|
||||||
var currentNode = root;
|
|
||||||
foreach (var i in Enumerable.Range(0, 100))
|
|
||||||
{
|
|
||||||
var newNode = new TreeNode(new Text(string.Empty));
|
|
||||||
currentNode.AddChild(newNode);
|
|
||||||
currentNode = newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tree = new Tree(root);
|
|
||||||
|
|
||||||
// When
|
|
||||||
var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
measurement.Min.ShouldBe(400);
|
|
||||||
measurement.Max.ShouldBe(80);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Measure_Leaf_Dominated_Width()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var root = new TreeNode(new Text("Root node"));
|
|
||||||
var currentNode = root;
|
|
||||||
foreach (var i in Enumerable.Range(0, 10))
|
|
||||||
{
|
|
||||||
var newNode = new TreeNode(new Text(i.ToString()));
|
|
||||||
currentNode.AddChild(newNode);
|
|
||||||
currentNode = newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tree = new Tree(root);
|
|
||||||
|
|
||||||
// When
|
|
||||||
var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
measurement.Min.ShouldBe(41);
|
|
||||||
measurement.Max.ShouldBe(41);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public Task Should_Render_Tree_Correctly()
|
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var console = new FakeConsole(width: 80);
|
var console = new FakeConsole(width: 80);
|
||||||
@ -101,7 +28,34 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
child3.AddChild(child3Child);
|
child3.AddChild(child3Child);
|
||||||
var children = new List<TreeNode> { new(new Text("child1"), nestedChildren), child2, child3 };
|
var children = new List<TreeNode> { new(new Text("child1"), nestedChildren), child2, child3 };
|
||||||
var root = new TreeNode(new Text("Root node"), children);
|
var root = new TreeNode(new Text("Root node"), children);
|
||||||
var tree = new Tree(root);
|
var tree = new Tree().AddChild(root);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(tree);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(console.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task Should_Render_Tree_With_Multiple_Roots_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new FakeConsole(width: 80);
|
||||||
|
var nestedChildren =
|
||||||
|
Enumerable.Range(0, 10)
|
||||||
|
.Select(x => new TreeNode(new Text($"multiple \n line {x}")));
|
||||||
|
var child2 = new TreeNode(new Text("child2"));
|
||||||
|
var child2Child = new TreeNode(new Text("child2Child"));
|
||||||
|
child2.AddChild(child2Child);
|
||||||
|
child2Child.AddChild(new TreeNode(new Text("Child 2 child\n child")));
|
||||||
|
var child3 = new TreeNode(new Text("child3"));
|
||||||
|
var child3Child = new TreeNode(new Text("single leaf\n multiline"));
|
||||||
|
child3Child.AddChild(new TreeNode(new Calendar(2020, 01)));
|
||||||
|
child3.AddChild(child3Child);
|
||||||
|
var children = new List<TreeNode> { new(new Text("child1"), nestedChildren), child2, child3 };
|
||||||
|
var root = new TreeNode(new Text("Root node"), children);
|
||||||
|
var tree = new Tree().AddChild(root).AddChild(child2Child);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(tree);
|
console.Render(tree);
|
||||||
@ -116,7 +70,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
// Given
|
// Given
|
||||||
var console = new FakeConsole(width: 80);
|
var console = new FakeConsole(width: 80);
|
||||||
var root = new TreeNode(new Text("Root node"), Enumerable.Empty<TreeNode>());
|
var root = new TreeNode(new Text("Root node"), Enumerable.Empty<TreeNode>());
|
||||||
var tree = new Tree(root);
|
var tree = new Tree().AddChild(root);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(tree);
|
console.Render(tree);
|
||||||
|
@ -9,10 +9,8 @@ namespace Spectre.Console
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Representation of tree data.
|
/// Representation of tree data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Tree : Renderable
|
public sealed class Tree : Renderable, IHasTreeNodes
|
||||||
{
|
{
|
||||||
private readonly TreeNode _rootNode;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the tree style.
|
/// Gets or sets the tree style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -23,13 +21,20 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public TreeAppearance Appearance { get; set; } = TreeAppearance.Ascii;
|
public TreeAppearance Appearance { get; set; } = TreeAppearance.Ascii;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the tree nodes.
|
||||||
|
/// </summary>
|
||||||
|
public List<TreeNode> Nodes { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
List<TreeNode> IHasTreeNodes.Children => Nodes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Tree"/> class.
|
/// Initializes a new instance of the <see cref="Tree"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rootNode">Root node of the tree to be rendered.</param>
|
public Tree()
|
||||||
public Tree(TreeNode rootNode)
|
|
||||||
{
|
{
|
||||||
_rootNode = rootNode ?? throw new ArgumentNullException(nameof(rootNode));
|
Nodes = new List<TreeNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -59,20 +64,52 @@ namespace Spectre.Console
|
|||||||
return new Measurement(currentMin, Math.Min(currentMax, maxWidth));
|
return new Measurement(currentMin, Math.Min(currentMax, maxWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MeasureAtDepth(context, maxWidth, _rootNode, depth: 0);
|
if (Nodes.Count == 1)
|
||||||
|
{
|
||||||
|
return MeasureAtDepth(context, maxWidth, Nodes[0], depth: 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var root = new TreeNode(Text.Empty);
|
||||||
|
foreach (var node in Nodes)
|
||||||
|
{
|
||||||
|
root.AddChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MeasureAtDepth(context, maxWidth, root, depth: 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
return _rootNode
|
if (Nodes.Count == 1)
|
||||||
|
{
|
||||||
|
// Single root
|
||||||
|
return Nodes[0]
|
||||||
.Render(context, maxWidth)
|
.Render(context, maxWidth)
|
||||||
.Concat(new List<Segment> { Segment.LineBreak })
|
.Concat(new List<Segment> { Segment.LineBreak })
|
||||||
.Concat(RenderChildren(context, maxWidth - Appearance.PartSize, _rootNode, depth: 0));
|
.Concat(RenderChildren(context, maxWidth - Appearance.PartSize, Nodes[0], depth: 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Multiple roots
|
||||||
|
var root = new TreeNode(Text.Empty);
|
||||||
|
foreach (var node in Nodes)
|
||||||
|
{
|
||||||
|
root.AddChild(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Segment> RenderChildren(RenderContext context, int maxWidth, TreeNode node, int depth,
|
return Enumerable.Empty<Segment>()
|
||||||
int? trailingStarted = null)
|
.Concat(RenderChildren(
|
||||||
|
context, maxWidth - Appearance.PartSize, root,
|
||||||
|
depth: 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Segment> RenderChildren(
|
||||||
|
RenderContext context, int maxWidth, TreeNode node,
|
||||||
|
int depth, int? trailingStarted = null)
|
||||||
{
|
{
|
||||||
var result = new List<Segment>();
|
var result = new List<Segment>();
|
||||||
foreach (var (_, _, lastChild, childNode) in node.Children.Enumerate())
|
foreach (var (_, _, lastChild, childNode) in node.Children.Enumerate())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user