mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4d1796e40 | ||
|
|
2dd0eb9f74 | ||
|
|
fa85216554 | ||
|
|
d475e3b30a | ||
|
|
9637066927 | ||
|
|
0b4321115a | ||
|
|
5cd9ece31a | ||
|
|
b0341862cf | ||
|
|
2e7b3d520a | ||
|
|
646f51a628 | ||
|
|
a0bd481255 | ||
|
|
6d197c5140 | ||
|
|
108e56c229 | ||
|
|
66994cd904 | ||
|
|
f9bd936254 | ||
|
|
a068fc68c3 | ||
|
|
aa34c145b9 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -2,9 +2,7 @@ name: Continuous Integration
|
|||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
|
||||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
# Disable sending usage data to Microsoft
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -28,10 +26,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 'Get Git tags'
|
|
||||||
run: git fetch --tags
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Setup dotnet
|
- name: Setup dotnet
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
10
.github/workflows/publish.yaml
vendored
10
.github/workflows/publish.yaml
vendored
@@ -8,9 +8,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
|
||||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
# Disable sending usage data to Microsoft
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -39,10 +37,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 'Get Git tags'
|
|
||||||
run: git fetch --tags
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Setup dotnet
|
- name: Setup dotnet
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
@@ -69,10 +63,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 'Get Git tags'
|
|
||||||
run: git fetch --tags
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Setup dotnet
|
- name: Setup dotnet
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# `Spectre.Console`
|
# `Spectre.Console`
|
||||||
|
|
||||||
_[](https://www.nuget.org/packages/spectre.console)_
|
_[](https://www.nuget.org/packages/spectre.console)_
|
||||||
|
|
||||||
A .NET Standard 2.0 library that makes it easier to create beautiful console applications.
|
A .NET Standard 2.0 library that makes it easier to create beautiful console applications.
|
||||||
It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)
|
It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)
|
||||||
|
|||||||
BIN
gfx/large-logo.png
Normal file
BIN
gfx/large-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 KiB |
BIN
gfx/medium-logo.png
Normal file
BIN
gfx/medium-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
gfx/small-logo.png
Normal file
BIN
gfx/small-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -18,6 +18,7 @@
|
|||||||
<Authors>Patrik Svensson</Authors>
|
<Authors>Patrik Svensson</Authors>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl>
|
<RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl>
|
||||||
|
<PackageIcon>small-logo.png</PackageIcon>
|
||||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||||
<PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|||||||
@@ -13,15 +13,22 @@ namespace Sample
|
|||||||
AnsiConsole.WriteLine("Hello World!");
|
AnsiConsole.WriteLine("Hello World!");
|
||||||
AnsiConsole.Reset();
|
AnsiConsole.Reset();
|
||||||
AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities);
|
AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities);
|
||||||
|
AnsiConsole.MarkupLine("Encoding: [yellow underline]{0}[/]", AnsiConsole.Console.Encoding.EncodingName);
|
||||||
AnsiConsole.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height);
|
AnsiConsole.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height);
|
||||||
AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
// We can also use System.ConsoleColor with AnsiConsole.
|
// We can also use System.ConsoleColor with AnsiConsole
|
||||||
|
// to set the foreground and background color.
|
||||||
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
||||||
{
|
{
|
||||||
AnsiConsole.Foreground = value;
|
var foreground = value;
|
||||||
AnsiConsole.WriteLine("ConsoleColor.{0}", value);
|
var background = (ConsoleColor)(15 - (int)value);
|
||||||
|
|
||||||
|
AnsiConsole.Foreground = foreground;
|
||||||
|
AnsiConsole.Background = background;
|
||||||
|
AnsiConsole.WriteLine("{0} on {1}", foreground, background);
|
||||||
|
AnsiConsole.ResetColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can get the default console via the static API.
|
// We can get the default console via the static API.
|
||||||
@@ -45,39 +52,118 @@ namespace Sample
|
|||||||
console.ResetColors();
|
console.ResetColors();
|
||||||
console.ResetDecoration();
|
console.ResetDecoration();
|
||||||
console.MarkupLine("Capabilities: [yellow underline]{0}[/]", console.Capabilities);
|
console.MarkupLine("Capabilities: [yellow underline]{0}[/]", console.Capabilities);
|
||||||
|
console.MarkupLine("Encoding: [yellow underline]{0}[/]", AnsiConsole.Console.Encoding.EncodingName);
|
||||||
console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", console.Width, console.Height);
|
console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", console.Width, console.Height);
|
||||||
console.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
console.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
|
|
||||||
// Nest some panels and text
|
// Nest some panels and text
|
||||||
AnsiConsole.Foreground = Color.Maroon;
|
AnsiConsole.Foreground = Color.Maroon;
|
||||||
AnsiConsole.Render(new Panel(new Panel(new Panel(new Panel(
|
AnsiConsole.Render(
|
||||||
Text.New(
|
new Panel(
|
||||||
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
new Panel(
|
||||||
"So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" +
|
new Panel(
|
||||||
"😅",
|
new Panel(
|
||||||
foreground: Color.White), content: Justify.Center)))));
|
Text.New(
|
||||||
|
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
||||||
|
"So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" +
|
||||||
|
"😅", foreground: Color.White))
|
||||||
|
{ Alignment = Justify.Center, Border = BorderKind.Rounded })))
|
||||||
|
{
|
||||||
|
Border = BorderKind.Ascii
|
||||||
|
});
|
||||||
|
|
||||||
// Reset colors
|
// Reset colors
|
||||||
AnsiConsole.ResetColors();
|
AnsiConsole.ResetColors();
|
||||||
|
|
||||||
// Left adjusted panel with text
|
// Left adjusted panel with text
|
||||||
AnsiConsole.Render(new Panel(
|
AnsiConsole.Render(new Panel(
|
||||||
Text.New("Left adjusted\nLeft",
|
Text.New("Left adjusted\nLeft"))
|
||||||
foreground: Color.White),
|
{
|
||||||
fit: true));
|
Expand = true,
|
||||||
|
Alignment = Justify.Left,
|
||||||
|
});
|
||||||
|
|
||||||
// Centered panel with text
|
// Centered panel with text
|
||||||
AnsiConsole.Render(new Panel(
|
AnsiConsole.Render(new Panel(
|
||||||
Text.New("Centered\nCenter",
|
Text.New("Centered\nCenter"))
|
||||||
foreground: Color.White),
|
{
|
||||||
fit: true, content: Justify.Center));
|
Expand = true,
|
||||||
|
Alignment = Justify.Center,
|
||||||
|
});
|
||||||
|
|
||||||
// Right adjusted panel with text
|
// Right adjusted panel with text
|
||||||
AnsiConsole.Render(new Panel(
|
AnsiConsole.Render(new Panel(
|
||||||
Text.New("Right adjusted\nRight",
|
Text.New("Right adjusted\nRight"))
|
||||||
foreground: Color.White),
|
{
|
||||||
fit: true, content: Justify.Right));
|
Expand = true,
|
||||||
|
Alignment = Justify.Right,
|
||||||
|
});
|
||||||
|
|
||||||
|
// A normal, square table
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||||
|
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||||
|
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||||
|
table.AddRow("Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||||
|
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||||
|
AnsiConsole.Render(table);
|
||||||
|
|
||||||
|
// A rounded table
|
||||||
|
table = new Table { Border = BorderKind.Rounded };
|
||||||
|
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||||
|
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||||
|
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||||
|
table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||||
|
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||||
|
AnsiConsole.Render(table);
|
||||||
|
|
||||||
|
// A rounded table without headers
|
||||||
|
table = new Table { Border = BorderKind.Rounded, ShowHeaders = false };
|
||||||
|
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||||
|
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||||
|
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||||
|
table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||||
|
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||||
|
AnsiConsole.Render(table);
|
||||||
|
|
||||||
|
// Emulate the usage information for "dotnet run"
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||||
|
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddRow("Options:", "", "");
|
||||||
|
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
|
||||||
|
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||||
|
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the MSBuild verbosity level. Allowed values are \nq[grey][[uiet][/], m[grey][[inimal][/], n[grey][[ormal][/], d[grey][[etailed][/], and diag[grey][[nostic][/].");
|
||||||
|
AnsiConsole.Render(grid);
|
||||||
|
|
||||||
|
// A simple table
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
table = new Table { Border = BorderKind.Rounded };
|
||||||
|
table.AddColumn("Foo");
|
||||||
|
table.AddColumn("Bar");
|
||||||
|
table.AddColumn("Baz");
|
||||||
|
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
AnsiConsole.Render(table);
|
||||||
|
|
||||||
|
// Render a table in some panels.
|
||||||
|
AnsiConsole.Render(new Panel(new Panel(table) { Border = BorderKind.Ascii }) { Padding = new Padding(0, 0) });
|
||||||
|
|
||||||
|
// Draw another table
|
||||||
|
table = new Table { Expand = false };
|
||||||
|
table.AddColumn(new TableColumn("Date"));
|
||||||
|
table.AddColumn(new TableColumn("Title"));
|
||||||
|
table.AddColumn(new TableColumn("Production\nBudget"));
|
||||||
|
table.AddColumn(new TableColumn("Box Office"));
|
||||||
|
table.AddRow("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "[red]$375,126,118[/]");
|
||||||
|
table.AddRow("May 25, 2018", "[yellow]Solo[/]: A Star Wars Story", "$275,000,000", "$393,151,347");
|
||||||
|
table.AddRow("Dec 15, 2017", "Star Wars Ep. VIII: The Last Jedi", "$262,000,000", "[bold green]$1,332,539,889[/]");
|
||||||
|
AnsiConsole.Render(table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace Spectre.Console.Tests
|
|||||||
{
|
{
|
||||||
public sealed class PlainConsole : IAnsiConsole, IDisposable
|
public sealed class PlainConsole : IAnsiConsole, IDisposable
|
||||||
{
|
{
|
||||||
public Capabilities Capabilities => throw new NotSupportedException();
|
public Capabilities Capabilities { get; }
|
||||||
public Encoding Encoding { get; }
|
public Encoding Encoding { get; }
|
||||||
|
|
||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
@@ -21,11 +21,15 @@ namespace Spectre.Console.Tests
|
|||||||
public string Output => Writer.ToString().TrimEnd('\n');
|
public string Output => Writer.ToString().TrimEnd('\n');
|
||||||
public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' });
|
public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' });
|
||||||
|
|
||||||
public PlainConsole(int width = 80, int height = 9000, Encoding encoding = null)
|
public PlainConsole(
|
||||||
|
int width = 80, int height = 9000, Encoding encoding = null,
|
||||||
|
bool supportsAnsi = true, ColorSystem colorSystem = ColorSystem.Standard,
|
||||||
|
bool legacyConsole = false)
|
||||||
{
|
{
|
||||||
|
Capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
|
||||||
|
Encoding = encoding ?? Encoding.UTF8;
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
Encoding = encoding ?? Encoding.UTF8;
|
|
||||||
Writer = new StringWriter();
|
Writer = new StringWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,38 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
|
|
||||||
public sealed class WriteLine
|
public sealed class WriteLine
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Reset_Colors_Correctly_After_Line_Break()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||||
|
|
||||||
|
// When
|
||||||
|
fixture.Console.Background = ConsoleColor.Red;
|
||||||
|
fixture.Console.WriteLine("Hello");
|
||||||
|
fixture.Console.Background = ConsoleColor.Green;
|
||||||
|
fixture.Console.WriteLine("World");
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fixture.Output.NormalizeLineEndings()
|
||||||
|
.ShouldBe("[101mHello[0m\n[102mWorld[0m\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||||
|
|
||||||
|
// When
|
||||||
|
fixture.Console.Background = ConsoleColor.Red;
|
||||||
|
fixture.Console.WriteLine("Hello\nWorld");
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fixture.Output.NormalizeLineEndings()
|
||||||
|
.ShouldBe("[101mHello[0m\n[101mWorld[0m\n");
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(AnsiSupport.Yes)]
|
[InlineData(AnsiSupport.Yes)]
|
||||||
[InlineData(AnsiSupport.No)]
|
[InlineData(AnsiSupport.No)]
|
||||||
|
|||||||
@@ -78,6 +78,20 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Shourd_Not_Consider_Black_And_Default_Colors_Equal()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var color1 = Color.Default;
|
||||||
|
var color2 = Color.Black;
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = color1.Equals(color2);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TheGetHashCodeMethod
|
public sealed class TheGetHashCodeMethod
|
||||||
|
|||||||
42
src/Spectre.Console.Tests/Unit/Composition/BorderTests.cs
Normal file
42
src/Spectre.Console.Tests/Unit/Composition/BorderTests.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Spectre.Console.Composition;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit.Composition
|
||||||
|
{
|
||||||
|
public sealed class BorderTests
|
||||||
|
{
|
||||||
|
public sealed class TheGetBorderMethod
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(BorderKind.None, false, typeof(NoBorder))]
|
||||||
|
[InlineData(BorderKind.Ascii, false, typeof(AsciiBorder))]
|
||||||
|
[InlineData(BorderKind.Square, false, typeof(SquareBorder))]
|
||||||
|
[InlineData(BorderKind.Rounded, false, typeof(RoundedBorder))]
|
||||||
|
[InlineData(BorderKind.None, true, typeof(NoBorder))]
|
||||||
|
[InlineData(BorderKind.Ascii, true, typeof(AsciiBorder))]
|
||||||
|
[InlineData(BorderKind.Square, true, typeof(SquareBorder))]
|
||||||
|
[InlineData(BorderKind.Rounded, true, typeof(SquareBorder))]
|
||||||
|
public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, bool safe, Type expected)
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Border.GetBorder(kind, safe);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Unknown_Border_Kind_Is_Specified()
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue, false));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
result.Message.ShouldBe("Unknown border kind");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/Spectre.Console.Tests/Unit/Composition/GridTests.cs
Normal file
145
src/Spectre.Console.Tests/Unit/Composition/GridTests.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit.Composition
|
||||||
|
{
|
||||||
|
public sealed class GridTests
|
||||||
|
{
|
||||||
|
public sealed class TheAddRowMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Rows_Are_Null()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var grid = new Grid();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => grid.AddRow(null));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
|
.ParamName.ShouldBe("columns");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddColumn();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => grid.AddRow("Foo"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
result.Message.ShouldBe("The number of row columns are less than the number of grid columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => grid.AddRow("Foo", "Bar"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
result.Message.ShouldBe("The number of row columns are greater than the number of grid columns.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Grid_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
grid.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(grid);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(2);
|
||||||
|
console.Lines[0].ShouldBe("Qux Corgi Waldo");
|
||||||
|
console.Lines[1].ShouldBe("Grault Garply Fred ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Grid_Column_Alignment_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn { Alignment = Justify.Right });
|
||||||
|
grid.AddColumn(new GridColumn { Alignment = Justify.Center });
|
||||||
|
grid.AddColumn(new GridColumn { Alignment = Justify.Left });
|
||||||
|
grid.AddRow("Foo", "Bar", "Baz");
|
||||||
|
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
grid.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(grid);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||||
|
console.Lines[1].ShouldBe(" Qux Corgi Waldo");
|
||||||
|
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Grid_Column_Padding_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) });
|
||||||
|
grid.AddColumns(2);
|
||||||
|
grid.AddRow("Foo", "Bar", "Baz");
|
||||||
|
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
grid.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(grid);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||||
|
console.Lines[1].ShouldBe(" Qux Corgi Waldo");
|
||||||
|
console.Lines[2].ShouldBe(" Grault Garply Fred ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Grid()
|
||||||
|
{
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||||
|
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) });
|
||||||
|
grid.AddRow("[bold]Options[/]", string.Empty);
|
||||||
|
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
|
||||||
|
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(grid);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(4);
|
||||||
|
console.Lines[0].ShouldBe("Options ");
|
||||||
|
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||||
|
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||||
|
console.Lines[3].ShouldBe(" The default for most projects is Debug.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,25 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
console.Lines[2].ShouldBe("└─────────────┘");
|
console.Lines[2].ShouldBe("└─────────────┘");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Panel_With_Padding()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(new Panel(Text.New("Hello World"))
|
||||||
|
{
|
||||||
|
Padding = new Padding(3, 5),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("┌───────────────────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Hello World │");
|
||||||
|
console.Lines[2].ShouldBe("└───────────────────┘");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Render_Panel_With_Unicode_Correctly()
|
public void Should_Render_Panel_With_Unicode_Correctly()
|
||||||
{
|
{
|
||||||
@@ -62,8 +81,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
// Given
|
// Given
|
||||||
var console = new PlainConsole(width: 80);
|
var console = new PlainConsole(width: 80);
|
||||||
var text = new Panel(
|
var text = new Panel(
|
||||||
Text.New("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"),
|
Text.New("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"));
|
||||||
content: Justify.Center);
|
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(text);
|
console.Render(text);
|
||||||
@@ -71,7 +89,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(7);
|
console.Lines.Count.ShouldBe(7);
|
||||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||||
console.Lines[1].ShouldBe("│ I heard you like 📦 │");
|
console.Lines[1].ShouldBe("│ I heard you like 📦 │");
|
||||||
console.Lines[2].ShouldBe("│ │");
|
console.Lines[2].ShouldBe("│ │");
|
||||||
console.Lines[3].ShouldBe("│ │");
|
console.Lines[3].ShouldBe("│ │");
|
||||||
console.Lines[4].ShouldBe("│ │");
|
console.Lines[4].ShouldBe("│ │");
|
||||||
@@ -80,19 +98,23 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Fit_Panel_To_Parent_If_Enabled()
|
public void Should_Expand_Panel_If_Enabled()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var console = new PlainConsole(width: 25);
|
var console = new PlainConsole(width: 80);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(new Panel(Text.New("Hello World"), fit: true));
|
console.Render(new Panel(Text.New("Hello World"))
|
||||||
|
{
|
||||||
|
Expand = true,
|
||||||
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(3);
|
console.Lines.Count.ShouldBe(3);
|
||||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
console.Lines[0].Length.ShouldBe(80);
|
||||||
console.Lines[1].ShouldBe("│ Hello World │");
|
console.Lines[0].ShouldBe("┌──────────────────────────────────────────────────────────────────────────────┐");
|
||||||
console.Lines[2].ShouldBe("└───────────────────────┘");
|
console.Lines[1].ShouldBe("│ Hello World │");
|
||||||
|
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -102,7 +124,12 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
var console = new PlainConsole(width: 25);
|
var console = new PlainConsole(width: 25);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Right));
|
console.Render(
|
||||||
|
new Panel(
|
||||||
|
Text.New("Hello World").WithAlignment(Justify.Right))
|
||||||
|
{
|
||||||
|
Expand = true,
|
||||||
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(3);
|
console.Lines.Count.ShouldBe(3);
|
||||||
@@ -118,7 +145,12 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
var console = new PlainConsole(width: 25);
|
var console = new PlainConsole(width: 25);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Center));
|
console.Render(
|
||||||
|
new Panel(
|
||||||
|
Text.New("Hello World").WithAlignment(Justify.Center))
|
||||||
|
{
|
||||||
|
Expand = true,
|
||||||
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(3);
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
|||||||
330
src/Spectre.Console.Tests/Unit/Composition/TableTests.cs
Normal file
330
src/Spectre.Console.Tests/Unit/Composition/TableTests.cs
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit.Composition
|
||||||
|
{
|
||||||
|
public sealed class TableTests
|
||||||
|
{
|
||||||
|
public sealed class TheAddColumnMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Column_Is_Null()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var table = new Table();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => table.AddColumn((string)null));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
|
.ParamName.ShouldBe("column");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TheAddColumnsMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Columns_Are_Null()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var table = new Table();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => table.AddColumns((string[])null));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
|
.ParamName.ShouldBe("columns");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TheAddRowMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Rows_Are_Null()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var table = new Table();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => table.AddRow(null));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
|
.ParamName.ShouldBe("columns");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumn("Hello");
|
||||||
|
table.AddColumn("World");
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => table.AddRow("Foo"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
result.Message.ShouldBe("The number of row columns are less than the number of table columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumn("Hello");
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => table.AddRow("Foo", "Bar"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
result.Message.ShouldBe("The number of row columns are greater than the number of table columns.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(6);
|
||||||
|
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
|
console.Lines[5].ShouldBe("└────────┴────────┴───────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_Nested_In_Panels_Correctly()
|
||||||
|
{
|
||||||
|
// A simple table
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table() { Border = BorderKind.Rounded };
|
||||||
|
table.AddColumn("Foo");
|
||||||
|
table.AddColumn("Bar");
|
||||||
|
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
|
||||||
|
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// Render a table in some panels.
|
||||||
|
console.Render(new Panel(new Panel(table)
|
||||||
|
{
|
||||||
|
Border = BorderKind.Ascii,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(11);
|
||||||
|
console.Lines[00].ShouldBe("┌───────────────────────────────────┐");
|
||||||
|
console.Lines[01].ShouldBe("│ +-------------------------------+ │");
|
||||||
|
console.Lines[02].ShouldBe("│ | ╭──────────┬────────┬───────╮ | │");
|
||||||
|
console.Lines[03].ShouldBe("│ | │ Foo │ Bar │ Baz │ | │");
|
||||||
|
console.Lines[04].ShouldBe("│ | ├──────────┼────────┼───────┤ | │");
|
||||||
|
console.Lines[05].ShouldBe("│ | │ Qux │ Corgi │ Waldo │ | │");
|
||||||
|
console.Lines[06].ShouldBe("│ | │ Quuuuuux │ │ │ | │");
|
||||||
|
console.Lines[07].ShouldBe("│ | │ Grault │ Garply │ Fred │ | │");
|
||||||
|
console.Lines[08].ShouldBe("│ | ╰──────────┴────────┴───────╯ | │");
|
||||||
|
console.Lines[09].ShouldBe("│ +-------------------------------+ │");
|
||||||
|
console.Lines[10].ShouldBe("└───────────────────────────────────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_Column_Justification_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left });
|
||||||
|
table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right });
|
||||||
|
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center });
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(6);
|
||||||
|
console.Lines[0].ShouldBe("┌────────┬────────┬────────────────────────────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├────────┼────────┼────────────────────────────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Grault │ Garply │ Lorem ipsum dolor sit amet │");
|
||||||
|
console.Lines[5].ShouldBe("└────────┴────────┴────────────────────────────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table() { Expand = true };
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(6);
|
||||||
|
console.Lines[0].Length.ShouldBe(80);
|
||||||
|
console.Lines[0].ShouldBe("┌───────────────────────────┬───────────────────────────┬──────────────────────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├───────────────────────────┼───────────────────────────┼──────────────────────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
|
console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_Ascii_Border_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table { Border = BorderKind.Ascii };
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(6);
|
||||||
|
console.Lines[0].ShouldBe("+-------------------------+");
|
||||||
|
console.Lines[1].ShouldBe("| Foo | Bar | Baz |");
|
||||||
|
console.Lines[2].ShouldBe("|--------+--------+-------|");
|
||||||
|
console.Lines[3].ShouldBe("| Qux | Corgi | Waldo |");
|
||||||
|
console.Lines[4].ShouldBe("| Grault | Garply | Fred |");
|
||||||
|
console.Lines[5].ShouldBe("+-------------------------+");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_Rounded_Border_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table { Border = BorderKind.Rounded };
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(6);
|
||||||
|
console.Lines[0].ShouldBe("╭────────┬────────┬───────╮");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
|
console.Lines[5].ShouldBe("╰────────┴────────┴───────╯");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_No_Border_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table { Border = BorderKind.None };
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
||||||
|
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
||||||
|
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||||
|
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
|
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_With_Cell_Padding_Correctly()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumns("Foo", "Bar");
|
||||||
|
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) });
|
||||||
|
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("┌────────┬────────┬──────────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("├────────┼────────┼──────────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
|
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||||
|
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
|
console.Lines[6].ShouldBe("└────────┴────────┴──────────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Table_Without_Footer_If_No_Rows_Are_Added()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var table = new Table();
|
||||||
|
table.AddColumns("Foo", "Bar");
|
||||||
|
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) });
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("┌─────┬─────┬────────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
|
console.Lines[2].ShouldBe("└─────┴─────┴────────┘");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
public void Should_Throw_If_Foreground_Is_Set_Twice()
|
public void Should_Throw_If_Foreground_Is_Set_Twice()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("green yellow", out var style);
|
var result = Style.TryParse("green yellow", out _);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
@@ -207,7 +207,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
public void Should_Throw_If_Background_Is_Set_Twice()
|
public void Should_Throw_If_Background_Is_Set_Twice()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("green on blue yellow", out var style);
|
var result = Style.TryParse("green on blue yellow", out _);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
@@ -217,7 +217,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("bold lol", out var style);
|
var result = Style.TryParse("bold lol", out _);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
@@ -227,7 +227,7 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
|
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("blue on lol", out var style);
|
var result = Style.TryParse("blue on lol", out _);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ namespace Spectre.Console
|
|||||||
{
|
{
|
||||||
private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() =>
|
private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() =>
|
||||||
{
|
{
|
||||||
return Create(new AnsiConsoleSettings
|
var console = Create(new AnsiConsoleSettings
|
||||||
{
|
{
|
||||||
Ansi = AnsiSupport.Detect,
|
Ansi = AnsiSupport.Detect,
|
||||||
ColorSystem = ColorSystemSupport.Detect,
|
ColorSystem = ColorSystemSupport.Detect,
|
||||||
Out = System.Console.Out,
|
Out = System.Console.Out,
|
||||||
});
|
});
|
||||||
|
Created = true;
|
||||||
|
return console;
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,6 +30,8 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static Capabilities Capabilities => Console.Capabilities;
|
public static Capabilities Capabilities => Console.Capabilities;
|
||||||
|
|
||||||
|
internal static bool Created { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the buffer width of the console.
|
/// Gets the buffer width of the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -16,16 +16,33 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ColorSystem ColorSystem { get; }
|
public ColorSystem ColorSystem { get; }
|
||||||
|
|
||||||
internal Capabilities(bool supportsAnsi, ColorSystem colorSystem)
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not
|
||||||
|
/// this is a legacy console (cmd.exe).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only relevant when running on Microsoft Windows.
|
||||||
|
/// </remarks>
|
||||||
|
public bool LegacyConsole { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Capabilities"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="supportsAnsi">Whether or not ANSI escape sequences are supported.</param>
|
||||||
|
/// <param name="colorSystem">The color system that is supported.</param>
|
||||||
|
/// <param name="legacyConsole">Whether or not this is a legacy console.</param>
|
||||||
|
public Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole)
|
||||||
{
|
{
|
||||||
SupportsAnsi = supportsAnsi;
|
SupportsAnsi = supportsAnsi;
|
||||||
ColorSystem = colorSystem;
|
ColorSystem = colorSystem;
|
||||||
|
LegacyConsole = legacyConsole;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
||||||
|
var legacyConsole = LegacyConsole ? "Legacy" : "Modern";
|
||||||
var bits = ColorSystem switch
|
var bits = ColorSystem switch
|
||||||
{
|
{
|
||||||
ColorSystem.NoColors => "1 bit",
|
ColorSystem.NoColors => "1 bit",
|
||||||
@@ -36,7 +53,7 @@ namespace Spectre.Console
|
|||||||
_ => "?"
|
_ => "?"
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem} ({bits})";
|
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ namespace Spectre.Console
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Equals(Color other)
|
public bool Equals(Color other)
|
||||||
{
|
{
|
||||||
return R == other.R && G == other.G && B == other.B;
|
return (IsDefault && other.IsDefault) ||
|
||||||
|
(IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
103
src/Spectre.Console/Composition/Border.cs
Normal file
103
src/Spectre.Console/Composition/Border.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a border used by tables.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Border
|
||||||
|
{
|
||||||
|
private readonly Dictionary<BorderPart, string> _lookup;
|
||||||
|
|
||||||
|
private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border>
|
||||||
|
{
|
||||||
|
{ BorderKind.None, new NoBorder() },
|
||||||
|
{ BorderKind.Ascii, new AsciiBorder() },
|
||||||
|
{ BorderKind.Square, new SquareBorder() },
|
||||||
|
{ BorderKind.Rounded, new RoundedBorder() },
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind>
|
||||||
|
{
|
||||||
|
{ BorderKind.Rounded, BorderKind.Square },
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Border"/> class.
|
||||||
|
/// </summary>
|
||||||
|
protected Border()
|
||||||
|
{
|
||||||
|
_lookup = Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kind">The kind of border to get.</param>
|
||||||
|
/// <param name="safe">Whether or not to get a "safe" border that can be rendered in a legacy console.</param>
|
||||||
|
/// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns>
|
||||||
|
public static Border GetBorder(BorderKind kind, bool safe)
|
||||||
|
{
|
||||||
|
if (safe && _safeLookup.TryGetValue(kind, out var safeKind))
|
||||||
|
{
|
||||||
|
kind = safeKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_borders.TryGetValue(kind, out var border))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unknown border kind");
|
||||||
|
}
|
||||||
|
|
||||||
|
return border;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<BorderPart, string> Initialize()
|
||||||
|
{
|
||||||
|
var lookup = new Dictionary<BorderPart, string>();
|
||||||
|
foreach (BorderPart part in Enum.GetValues(typeof(BorderPart)))
|
||||||
|
{
|
||||||
|
var text = GetBoxPart(part);
|
||||||
|
if (text.Length > 1)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup.Add(part, GetBoxPart(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the string representation of a specific border part.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="part">The part to get a string representation for.</param>
|
||||||
|
/// <param name="count">The number of repetitions.</param>
|
||||||
|
/// <returns>A string representation of the specified border part.</returns>
|
||||||
|
public string GetPart(BorderPart part, int count)
|
||||||
|
{
|
||||||
|
// TODO: This need some optimization...
|
||||||
|
return string.Join(string.Empty, Enumerable.Repeat(GetBoxPart(part)[0], count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the string representation of a specific border part.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="part">The part to get a string representation for.</param>
|
||||||
|
/// <returns>A string representation of the specified border part.</returns>
|
||||||
|
public string GetPart(BorderPart part)
|
||||||
|
{
|
||||||
|
return _lookup[part].ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the character representing the specified border part.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="part">The part to get the character representation for.</param>
|
||||||
|
/// <returns>A character representation of the specified border part.</returns>
|
||||||
|
protected abstract string GetBoxPart(BorderPart part);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents different kinds of borders.
|
||||||
|
/// </summary>
|
||||||
|
public enum BorderKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No border.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A square border.
|
||||||
|
/// </summary>
|
||||||
|
Square = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An old school ASCII border.
|
||||||
|
/// </summary>
|
||||||
|
Ascii = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A rounded border.
|
||||||
|
/// </summary>
|
||||||
|
Rounded = 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the different border parts.
|
||||||
|
/// </summary>
|
||||||
|
public enum BorderPart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The top left part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderTopLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The top part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderTop,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The top separator part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderTopSeparator,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The top right part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderTopRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The left part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A header separator.
|
||||||
|
/// </summary>
|
||||||
|
HeaderSeparator,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The right part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom left part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderBottomLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderBottom,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom separator part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderBottomSeparator,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom right part of a header.
|
||||||
|
/// </summary>
|
||||||
|
HeaderBottomRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The left part of a cell.
|
||||||
|
/// </summary>
|
||||||
|
CellLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A cell separator.
|
||||||
|
/// </summary>
|
||||||
|
CellSeparator,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The right part of a cell.
|
||||||
|
/// </summary>
|
||||||
|
CellRight,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom left part of a footer.
|
||||||
|
/// </summary>
|
||||||
|
FooterBottomLeft,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom part of a footer.
|
||||||
|
/// </summary>
|
||||||
|
FooterBottom,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom separator part of a footer.
|
||||||
|
/// </summary>
|
||||||
|
FooterBottomSeparator,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bottom right part of a footer.
|
||||||
|
/// </summary>
|
||||||
|
FooterBottomRight,
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an old school ASCII border.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AsciiBorder : Border
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string GetBoxPart(BorderPart part)
|
||||||
|
{
|
||||||
|
return part switch
|
||||||
|
{
|
||||||
|
BorderPart.HeaderTopLeft => "+",
|
||||||
|
BorderPart.HeaderTop => "-",
|
||||||
|
BorderPart.HeaderTopSeparator => "-",
|
||||||
|
BorderPart.HeaderTopRight => "+",
|
||||||
|
BorderPart.HeaderLeft => "|",
|
||||||
|
BorderPart.HeaderSeparator => "|",
|
||||||
|
BorderPart.HeaderRight => "|",
|
||||||
|
BorderPart.HeaderBottomLeft => "|",
|
||||||
|
BorderPart.HeaderBottom => "-",
|
||||||
|
BorderPart.HeaderBottomSeparator => "+",
|
||||||
|
BorderPart.HeaderBottomRight => "|",
|
||||||
|
BorderPart.CellLeft => "|",
|
||||||
|
BorderPart.CellSeparator => "|",
|
||||||
|
BorderPart.CellRight => "|",
|
||||||
|
BorderPart.FooterBottomLeft => "+",
|
||||||
|
BorderPart.FooterBottom => "-",
|
||||||
|
BorderPart.FooterBottomSeparator => "-",
|
||||||
|
BorderPart.FooterBottomRight => "+",
|
||||||
|
_ => throw new InvalidOperationException("Unknown box part."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an invisible border.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class NoBorder : Border
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string GetBoxPart(BorderPart part)
|
||||||
|
{
|
||||||
|
return " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a rounded border.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RoundedBorder : Border
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string GetBoxPart(BorderPart part)
|
||||||
|
{
|
||||||
|
return part switch
|
||||||
|
{
|
||||||
|
BorderPart.HeaderTopLeft => "╭",
|
||||||
|
BorderPart.HeaderTop => "─",
|
||||||
|
BorderPart.HeaderTopSeparator => "┬",
|
||||||
|
BorderPart.HeaderTopRight => "╮",
|
||||||
|
BorderPart.HeaderLeft => "│",
|
||||||
|
BorderPart.HeaderSeparator => "│",
|
||||||
|
BorderPart.HeaderRight => "│",
|
||||||
|
BorderPart.HeaderBottomLeft => "├",
|
||||||
|
BorderPart.HeaderBottom => "─",
|
||||||
|
BorderPart.HeaderBottomSeparator => "┼",
|
||||||
|
BorderPart.HeaderBottomRight => "┤",
|
||||||
|
BorderPart.CellLeft => "│",
|
||||||
|
BorderPart.CellSeparator => "│",
|
||||||
|
BorderPart.CellRight => "│",
|
||||||
|
BorderPart.FooterBottomLeft => "╰",
|
||||||
|
BorderPart.FooterBottom => "─",
|
||||||
|
BorderPart.FooterBottomSeparator => "┴",
|
||||||
|
BorderPart.FooterBottomRight => "╯",
|
||||||
|
_ => throw new InvalidOperationException("Unknown box part."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a square border.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SquareBorder : Border
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override string GetBoxPart(BorderPart part)
|
||||||
|
{
|
||||||
|
return part switch
|
||||||
|
{
|
||||||
|
BorderPart.HeaderTopLeft => "┌",
|
||||||
|
BorderPart.HeaderTop => "─",
|
||||||
|
BorderPart.HeaderTopSeparator => "┬",
|
||||||
|
BorderPart.HeaderTopRight => "┐",
|
||||||
|
BorderPart.HeaderLeft => "│",
|
||||||
|
BorderPart.HeaderSeparator => "│",
|
||||||
|
BorderPart.HeaderRight => "│",
|
||||||
|
BorderPart.HeaderBottomLeft => "├",
|
||||||
|
BorderPart.HeaderBottom => "─",
|
||||||
|
BorderPart.HeaderBottomSeparator => "┼",
|
||||||
|
BorderPart.HeaderBottomRight => "┤",
|
||||||
|
BorderPart.CellLeft => "│",
|
||||||
|
BorderPart.CellSeparator => "│",
|
||||||
|
BorderPart.CellRight => "│",
|
||||||
|
BorderPart.FooterBottomLeft => "└",
|
||||||
|
BorderPart.FooterBottom => "─",
|
||||||
|
BorderPart.FooterBottomSeparator => "┴",
|
||||||
|
BorderPart.FooterBottomRight => "┘",
|
||||||
|
_ => throw new InvalidOperationException("Unknown box part."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/Spectre.Console/Composition/Grid.cs
Normal file
120
src/Spectre.Console/Composition/Grid.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Spectre.Console.Composition;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a grid.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Grid : IRenderable
|
||||||
|
{
|
||||||
|
private readonly Table _table;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Grid"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public Grid()
|
||||||
|
{
|
||||||
|
_table = new Table
|
||||||
|
{
|
||||||
|
Border = BorderKind.None,
|
||||||
|
ShowHeaders = false,
|
||||||
|
IsGrid = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Measurement Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
return ((IRenderable)_table).Measure(context, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<Segment> Render(RenderContext context, int width)
|
||||||
|
{
|
||||||
|
return ((IRenderable)_table).Render(context, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the grid.
|
||||||
|
/// </summary>
|
||||||
|
public void AddColumn()
|
||||||
|
{
|
||||||
|
AddColumn(new GridColumn());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the grid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The column to add.</param>
|
||||||
|
public void AddColumn(GridColumn column)
|
||||||
|
{
|
||||||
|
if (column is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
_table.AddColumn(new TableColumn(string.Empty)
|
||||||
|
{
|
||||||
|
Width = column.Width,
|
||||||
|
NoWrap = column.NoWrap,
|
||||||
|
Padding = column.Padding,
|
||||||
|
Alignment = column.Alignment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the grid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The number of columns to add.</param>
|
||||||
|
public void AddColumns(int count)
|
||||||
|
{
|
||||||
|
for (var index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
AddColumn(new GridColumn());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the grid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The columns to add.</param>
|
||||||
|
public void AddColumns(params GridColumn[] columns)
|
||||||
|
{
|
||||||
|
if (columns is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
AddColumn(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new row to the grid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The columns to add.</param>
|
||||||
|
public void AddRow(params string[] columns)
|
||||||
|
{
|
||||||
|
if (columns is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Length < _table.ColumnCount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The number of row columns are less than the number of grid columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Length > _table.ColumnCount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_table.AddRow(columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/Spectre.Console/Composition/GridColumn.cs
Normal file
30
src/Spectre.Console/Composition/GridColumn.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a grid column.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GridColumn
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the width of the column.
|
||||||
|
/// If <c>null</c>, the column will adapt to it's contents.
|
||||||
|
/// </summary>
|
||||||
|
public int? Width { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether wrapping of
|
||||||
|
/// text within the column should be prevented.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoWrap { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the padding of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Padding Padding { get; set; } = new Padding(0, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alignment of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Alignment { get; set; } = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Spectre.Console.Composition
|
namespace Spectre.Console.Composition
|
||||||
{
|
{
|
||||||
@@ -11,17 +10,17 @@ namespace Spectre.Console.Composition
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Measures the renderable object.
|
/// Measures the renderable object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="encoding">The encoding to use.</param>
|
/// <param name="context">The render context.</param>
|
||||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||||
/// <returns>The width of the object.</returns>
|
/// <returns>The minimum and maximum width of the object.</returns>
|
||||||
int Measure(Encoding encoding, int maxWidth);
|
Measurement Measure(RenderContext context, int maxWidth);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Renders the object.
|
/// Renders the object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="encoding">The encoding to use.</param>
|
/// <param name="context">The render context.</param>
|
||||||
/// <param name="width">The width of the render area.</param>
|
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||||
/// <returns>A collection of segments.</returns>
|
/// <returns>A collection of segments.</returns>
|
||||||
IEnumerable<Segment> Render(Encoding encoding, int width);
|
IEnumerable<Segment> Render(RenderContext context, int maxWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a measurement.
|
||||||
|
/// </summary>
|
||||||
|
public struct Measurement : IEquatable<Measurement>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the minimum width.
|
||||||
|
/// </summary>
|
||||||
|
public int Min { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum width.
|
||||||
|
/// </summary>
|
||||||
|
public int Max { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Measurement"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="min">The minimum width.</param>
|
||||||
|
/// <param name="max">The maximum width.</param>
|
||||||
|
public Measurement(int min, int max)
|
||||||
|
{
|
||||||
|
Min = min;
|
||||||
|
Max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is Measurement measurement && Equals(measurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
var hash = (int)2166136261;
|
||||||
|
hash = (hash * 16777619) ^ Min.GetHashCode();
|
||||||
|
hash = (hash * 16777619) ^ Max.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(Measurement other)
|
||||||
|
{
|
||||||
|
return Min == other.Min && Max == other.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Measurement"/> instances are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first measurement instance to compare.</param>
|
||||||
|
/// <param name="right">The second measurement instance to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns>
|
||||||
|
public static bool operator ==(Measurement left, Measurement right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Measurement"/> instances are not equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first measurement instance to compare.</param>
|
||||||
|
/// <param name="right">The second measurement instance to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns>
|
||||||
|
public static bool operator !=(Measurement left, Measurement right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/Spectre.Console/Composition/Padding.cs
Normal file
86
src/Spectre.Console/Composition/Padding.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a measurement.
|
||||||
|
/// </summary>
|
||||||
|
public struct Padding : IEquatable<Padding>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the left padding.
|
||||||
|
/// </summary>
|
||||||
|
public int Left { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the right padding.
|
||||||
|
/// </summary>
|
||||||
|
public int Right { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Padding"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The left padding.</param>
|
||||||
|
/// <param name="right">The right padding.</param>
|
||||||
|
public Padding(int left, int right)
|
||||||
|
{
|
||||||
|
Left = left;
|
||||||
|
Right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is Padding padding && Equals(padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
var hash = (int)2166136261;
|
||||||
|
hash = (hash * 16777619) ^ Left.GetHashCode();
|
||||||
|
hash = (hash * 16777619) ^ Right.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Equals(Padding other)
|
||||||
|
{
|
||||||
|
return Left == other.Left && Right == other.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Padding"/> instances are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||||
|
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the two instances are equal, otherwise <c>false</c>.</returns>
|
||||||
|
public static bool operator ==(Padding left, Padding right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Padding"/> instances are not equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||||
|
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||||
|
/// <returns><c>true</c> if the two instances are not equal, otherwise <c>false</c>.</returns>
|
||||||
|
public static bool operator !=(Padding left, Padding right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the horizontal padding.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The horizontal padding.</returns>
|
||||||
|
public int GetHorizontalPadding()
|
||||||
|
{
|
||||||
|
return Left + Right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Spectre.Console.Composition;
|
using Spectre.Console.Composition;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
@@ -11,106 +10,120 @@ namespace Spectre.Console
|
|||||||
public sealed class Panel : IRenderable
|
public sealed class Panel : IRenderable
|
||||||
{
|
{
|
||||||
private readonly IRenderable _child;
|
private readonly IRenderable _child;
|
||||||
private readonly bool _fit;
|
|
||||||
private readonly Justify _content;
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not to use
|
||||||
|
/// a "safe" border on legacy consoles that might not be able
|
||||||
|
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool SafeBorder { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the kind of border to use.
|
||||||
|
/// </summary>
|
||||||
|
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alignment of the panel contents.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Alignment { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the panel should
|
||||||
|
/// fit the available space. If <c>false</c>, the panel width will be
|
||||||
|
/// auto calculated. Defaults to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool Expand { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the padding.
|
||||||
|
/// </summary>
|
||||||
|
public Padding Padding { get; set; } = new Padding(1, 1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Panel"/> class.
|
/// Initializes a new instance of the <see cref="Panel"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="child">The child.</param>
|
/// <param name="content">The panel content.</param>
|
||||||
/// <param name="fit">Whether or not to fit the panel to it's parent.</param>
|
public Panel(IRenderable content)
|
||||||
/// <param name="content">The justification of the panel content.</param>
|
|
||||||
public Panel(IRenderable child, bool fit = false, Justify content = Justify.Left)
|
|
||||||
{
|
{
|
||||||
_child = child;
|
_child = content ?? throw new System.ArgumentNullException(nameof(content));
|
||||||
_fit = fit;
|
|
||||||
_content = content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Measure(Encoding encoding, int maxWidth)
|
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
var childWidth = _child.Measure(encoding, maxWidth);
|
var childWidth = _child.Measure(context, maxWidth);
|
||||||
return childWidth + 4;
|
return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||||
{
|
{
|
||||||
var childWidth = width - 4;
|
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||||
if (!_fit)
|
|
||||||
|
var edgeWidth = 2;
|
||||||
|
var paddingWidth = Padding.GetHorizontalPadding();
|
||||||
|
var childWidth = width - edgeWidth - paddingWidth;
|
||||||
|
|
||||||
|
if (!Expand)
|
||||||
{
|
{
|
||||||
childWidth = _child.Measure(encoding, width - 2);
|
var measurement = _child.Measure(context, width - edgeWidth - paddingWidth);
|
||||||
|
childWidth = measurement.Max;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<Segment>();
|
var panelWidth = childWidth + paddingWidth;
|
||||||
var panelWidth = childWidth + 2;
|
|
||||||
|
|
||||||
result.Add(new Segment("┌"));
|
// Panel top
|
||||||
result.Add(new Segment(new string('─', panelWidth)));
|
var result = new List<Segment>
|
||||||
result.Add(new Segment("┐"));
|
{
|
||||||
result.Add(new Segment("\n"));
|
new Segment(border.GetPart(BorderPart.HeaderTopLeft)),
|
||||||
|
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)),
|
||||||
|
new Segment(border.GetPart(BorderPart.HeaderTopRight)),
|
||||||
|
new Segment("\n"),
|
||||||
|
};
|
||||||
|
|
||||||
// Render the child.
|
// Render the child.
|
||||||
var childSegments = _child.Render(encoding, childWidth);
|
var childContext = context.WithJustification(Alignment);
|
||||||
|
var childSegments = _child.Render(childContext, childWidth);
|
||||||
|
|
||||||
// Split the child segments into lines.
|
// Split the child segments into lines.
|
||||||
var lines = Segment.SplitLines(childSegments, childWidth);
|
foreach (var line in Segment.SplitLines(childSegments, panelWidth))
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
{
|
||||||
result.Add(new Segment("│ "));
|
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||||
|
|
||||||
|
// Left padding
|
||||||
|
if (Padding.Left > 0)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', Padding.Left)));
|
||||||
|
}
|
||||||
|
|
||||||
var content = new List<Segment>();
|
var content = new List<Segment>();
|
||||||
|
content.AddRange(line);
|
||||||
|
|
||||||
var length = line.Sum(segment => segment.CellLength(encoding));
|
// Do we need to pad the panel?
|
||||||
|
var length = line.Sum(segment => segment.CellLength(context.Encoding));
|
||||||
if (length < childWidth)
|
if (length < childWidth)
|
||||||
{
|
{
|
||||||
if (_content == Justify.Right)
|
var diff = childWidth - length;
|
||||||
{
|
content.Add(new Segment(new string(' ', diff)));
|
||||||
var diff = childWidth - length;
|
|
||||||
content.Add(new Segment(new string(' ', diff)));
|
|
||||||
}
|
|
||||||
else if (_content == Justify.Center)
|
|
||||||
{
|
|
||||||
var diff = (childWidth - length) / 2;
|
|
||||||
content.Add(new Segment(new string(' ', diff)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var segment in line)
|
|
||||||
{
|
|
||||||
content.Add(segment.StripLineEndings());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length < childWidth)
|
|
||||||
{
|
|
||||||
if (_content == Justify.Left)
|
|
||||||
{
|
|
||||||
var diff = childWidth - length;
|
|
||||||
content.Add(new Segment(new string(' ', diff)));
|
|
||||||
}
|
|
||||||
else if (_content == Justify.Center)
|
|
||||||
{
|
|
||||||
var diff = (childWidth - length) / 2;
|
|
||||||
content.Add(new Segment(new string(' ', diff)));
|
|
||||||
|
|
||||||
var remainder = (childWidth - length) % 2;
|
|
||||||
if (remainder != 0)
|
|
||||||
{
|
|
||||||
content.Add(new Segment(new string(' ', remainder)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.AddRange(content);
|
result.AddRange(content);
|
||||||
|
|
||||||
result.Add(new Segment(" │"));
|
// Right padding
|
||||||
|
if (Padding.Right > 0)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', Padding.Right)));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||||
result.Add(new Segment("\n"));
|
result.Add(new Segment("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(new Segment("└"));
|
// Panel bottom
|
||||||
result.Add(new Segment(new string('─', panelWidth)));
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||||
result.Add(new Segment("┘"));
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth)));
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||||
result.Add(new Segment("\n"));
|
result.Add(new Segment("\n"));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Composition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a render context.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RenderContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the console's output encoding.
|
||||||
|
/// </summary>
|
||||||
|
public Encoding Encoding { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not this a legacy console (i.e. cmd.exe).
|
||||||
|
/// </summary>
|
||||||
|
public bool LegacyConsole { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether or not unicode is supported.
|
||||||
|
/// </summary>
|
||||||
|
public bool Unicode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current justification.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Justification { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RenderContext"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoding">The console's output encoding.</param>
|
||||||
|
/// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param>
|
||||||
|
/// <param name="justification">The justification to use when rendering.</param>
|
||||||
|
public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null)
|
||||||
|
{
|
||||||
|
Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
|
||||||
|
LegacyConsole = legacyConsole;
|
||||||
|
Justification = justification;
|
||||||
|
Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new context with the specified justification.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="justification">The justification.</param>
|
||||||
|
/// <returns>A new <see cref="RenderContext"/> instance with the specified justification.</returns>
|
||||||
|
public RenderContext WithJustification(Justify? justification)
|
||||||
|
{
|
||||||
|
return new RenderContext(Encoding, LegacyConsole, justification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -211,5 +211,21 @@ namespace Spectre.Console.Composition
|
|||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||||
|
{
|
||||||
|
foreach (var cell in cells)
|
||||||
|
{
|
||||||
|
if (cell.Count < cellHeight)
|
||||||
|
{
|
||||||
|
while (cell.Count != cellHeight)
|
||||||
|
{
|
||||||
|
cell.Add(new SegmentLine());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
124
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Composition;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class Table
|
||||||
|
{
|
||||||
|
// Calculate the widths of each column, including padding, not including borders.
|
||||||
|
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||||
|
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||||
|
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
|
||||||
|
{
|
||||||
|
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth));
|
||||||
|
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||||
|
|
||||||
|
var tableWidth = widths.Sum();
|
||||||
|
|
||||||
|
if (tableWidth > maxWidth)
|
||||||
|
{
|
||||||
|
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||||
|
widths = CollapseWidths(widths, wrappable, maxWidth);
|
||||||
|
tableWidth = widths.Sum();
|
||||||
|
|
||||||
|
// last resort, reduce columns evenly
|
||||||
|
if (tableWidth > maxWidth)
|
||||||
|
{
|
||||||
|
var excessWidth = tableWidth - maxWidth;
|
||||||
|
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
|
||||||
|
tableWidth = widths.Sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableWidth < maxWidth && ShouldExpand())
|
||||||
|
{
|
||||||
|
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
|
||||||
|
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return widths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce widths so that the total is less or equal to the max width.
|
||||||
|
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||||
|
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
|
||||||
|
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
|
||||||
|
{
|
||||||
|
var totalWidth = widths.Sum();
|
||||||
|
var excessWidth = totalWidth - maxWidth;
|
||||||
|
|
||||||
|
if (wrappable.AnyTrue())
|
||||||
|
{
|
||||||
|
while (totalWidth != 0 && excessWidth > 0)
|
||||||
|
{
|
||||||
|
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
|
||||||
|
.Where(x => x.allowWrap)
|
||||||
|
.Max(x => x.width);
|
||||||
|
|
||||||
|
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max();
|
||||||
|
var columnDifference = maxColumn - secondMaxColumn;
|
||||||
|
|
||||||
|
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
|
||||||
|
if (!ratios.Any(x => x != 0) || columnDifference == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
|
||||||
|
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
|
||||||
|
|
||||||
|
totalWidth = widths.Sum();
|
||||||
|
excessWidth = totalWidth - maxWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return widths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
|
||||||
|
{
|
||||||
|
var padding = column.Padding.GetHorizontalPadding();
|
||||||
|
|
||||||
|
// Predetermined width?
|
||||||
|
if (column.Width != null)
|
||||||
|
{
|
||||||
|
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
var columnIndex = _columns.IndexOf(column);
|
||||||
|
var rows = _rows.Select(row => row[columnIndex]);
|
||||||
|
|
||||||
|
var minWidths = new List<int>();
|
||||||
|
var maxWidths = new List<int>();
|
||||||
|
|
||||||
|
// Include columns in measurement
|
||||||
|
var measure = ((IRenderable)column.Text).Measure(options, maxWidth);
|
||||||
|
minWidths.Add(measure.Min);
|
||||||
|
maxWidths.Add(measure.Max);
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
measure = ((IRenderable)row).Measure(options, maxWidth);
|
||||||
|
minWidths.Add(measure.Min);
|
||||||
|
maxWidths.Add(measure.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minWidths.Count > 0 ? minWidths.Max() : padding,
|
||||||
|
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetExtraWidth(bool includePadding)
|
||||||
|
{
|
||||||
|
var edges = 2;
|
||||||
|
var separators = _columns.Count - 1;
|
||||||
|
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||||
|
return separators + edges + padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
359
src/Spectre.Console/Composition/Table.cs
Normal file
359
src/Spectre.Console/Composition/Table.cs
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Composition;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class Table : IRenderable
|
||||||
|
{
|
||||||
|
private readonly List<TableColumn> _columns;
|
||||||
|
private readonly List<List<Text>> _rows;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of columns in the table.
|
||||||
|
/// </summary>
|
||||||
|
public int ColumnCount => _columns.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of rows in the table.
|
||||||
|
/// </summary>
|
||||||
|
public int RowCount => _rows.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the kind of border to use.
|
||||||
|
/// </summary>
|
||||||
|
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not table headers should be shown.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowHeaders { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the table should
|
||||||
|
/// fit the available space. If <c>false</c>, the table width will be
|
||||||
|
/// auto calculated. Defaults to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool Expand { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the width of the table.
|
||||||
|
/// </summary>
|
||||||
|
public int? Width { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not to use
|
||||||
|
/// a "safe" border on legacy consoles that might not be able
|
||||||
|
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool SafeBorder { get; set; } = true;
|
||||||
|
|
||||||
|
internal bool IsGrid { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public Table()
|
||||||
|
{
|
||||||
|
_columns = new List<TableColumn>();
|
||||||
|
_rows = new List<List<Text>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The column to add.</param>
|
||||||
|
public void AddColumn(string column)
|
||||||
|
{
|
||||||
|
if (column is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
_columns.Add(new TableColumn(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a column to the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The column to add.</param>
|
||||||
|
public void AddColumn(TableColumn column)
|
||||||
|
{
|
||||||
|
if (column is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
_columns.Add(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple columns to the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The columns to add.</param>
|
||||||
|
public void AddColumns(params string[] columns)
|
||||||
|
{
|
||||||
|
if (columns is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple columns to the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The columns to add.</param>
|
||||||
|
public void AddColumns(params TableColumn[] columns)
|
||||||
|
{
|
||||||
|
if (columns is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
_columns.AddRange(columns.Select(column => column));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a row to the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The row columns to add.</param>
|
||||||
|
public void AddRow(params string[] columns)
|
||||||
|
{
|
||||||
|
if (columns is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Length < _columns.Count)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The number of row columns are less than the number of table columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Length > _columns.Count)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_rows.Add(columns.Select(column => Text.New(column)).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
if (context is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Width != null)
|
||||||
|
{
|
||||||
|
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth -= GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
|
var measurements = _columns.Select(column => MeasureColumn(column, context, maxWidth)).ToList();
|
||||||
|
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||||
|
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
|
return new Measurement(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||||
|
{
|
||||||
|
if (context is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||||
|
|
||||||
|
var showBorder = Border != BorderKind.None;
|
||||||
|
var hideBorder = Border == BorderKind.None;
|
||||||
|
var hasRows = _rows.Count > 0;
|
||||||
|
|
||||||
|
var maxWidth = width;
|
||||||
|
if (Width != null)
|
||||||
|
{
|
||||||
|
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth -= GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
|
// Calculate the column and table widths
|
||||||
|
var columnWidths = CalculateColumnWidths(context, maxWidth);
|
||||||
|
|
||||||
|
// Update the table width.
|
||||||
|
width = columnWidths.Sum() + GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
|
var rows = new List<List<Text>>();
|
||||||
|
if (ShowHeaders)
|
||||||
|
{
|
||||||
|
// Add columns to top of rows
|
||||||
|
rows.Add(new List<Text>(_columns.Select(c => c.Text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rows.
|
||||||
|
rows.AddRange(_rows);
|
||||||
|
|
||||||
|
// Iterate all rows
|
||||||
|
var result = new List<Segment>();
|
||||||
|
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
|
||||||
|
{
|
||||||
|
var cellHeight = 1;
|
||||||
|
|
||||||
|
// Get the list of cells for the row and calculate the cell height
|
||||||
|
var cells = new List<List<SegmentLine>>();
|
||||||
|
foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
|
||||||
|
{
|
||||||
|
var justification = _columns[columnIndex].Alignment;
|
||||||
|
var childContext = context.WithJustification(justification);
|
||||||
|
|
||||||
|
var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth));
|
||||||
|
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||||
|
cells.Add(lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show top of header?
|
||||||
|
if (firstRow && showBorder)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||||
|
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
|
{
|
||||||
|
var padding = _columns[columnIndex].Padding;
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // Left padding
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding
|
||||||
|
|
||||||
|
if (!lastColumn)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
|
||||||
|
result.Add(Segment.LineBreak());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through each cell row
|
||||||
|
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
|
||||||
|
{
|
||||||
|
// Make cells the same shape
|
||||||
|
cells = Segment.MakeSameHeight(cellHeight, cells);
|
||||||
|
|
||||||
|
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
|
||||||
|
{
|
||||||
|
if (firstCell && showBorder)
|
||||||
|
{
|
||||||
|
// Show left column edge
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad column on left side.
|
||||||
|
if (showBorder || IsGrid)
|
||||||
|
{
|
||||||
|
var leftPadding = _columns[cellIndex].Padding.Left;
|
||||||
|
if (leftPadding > 0)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', leftPadding)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add content
|
||||||
|
result.AddRange(cell[cellRowIndex]);
|
||||||
|
|
||||||
|
// Pad cell content right
|
||||||
|
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding));
|
||||||
|
if (length < columnWidths[cellIndex])
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad column on the right side
|
||||||
|
if (showBorder || (hideBorder && !lastCell) || (IsGrid && !lastCell))
|
||||||
|
{
|
||||||
|
var rightPadding = _columns[cellIndex].Padding.Right;
|
||||||
|
if (rightPadding > 0)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', rightPadding)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCell && showBorder)
|
||||||
|
{
|
||||||
|
// Add right column edge
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||||
|
}
|
||||||
|
else if (showBorder || (hideBorder && !lastCell))
|
||||||
|
{
|
||||||
|
// Add column separator
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(Segment.LineBreak());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show header separator?
|
||||||
|
if (firstRow && showBorder && ShowHeaders && hasRows)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||||
|
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
|
{
|
||||||
|
var padding = _columns[columnIndex].Padding;
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // Left padding
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding
|
||||||
|
|
||||||
|
if (!lastColumn)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
|
||||||
|
result.Add(Segment.LineBreak());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show bottom of footer?
|
||||||
|
if (lastRow && showBorder)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||||
|
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
|
{
|
||||||
|
var padding = _columns[columnIndex].Padding;
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding
|
||||||
|
|
||||||
|
if (!lastColumn)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||||
|
result.Add(Segment.LineBreak());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldExpand()
|
||||||
|
{
|
||||||
|
return Expand || Width != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Spectre.Console/Composition/TableColumn.cs
Normal file
50
src/Spectre.Console/Composition/TableColumn.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table column.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TableColumn
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text associated with the column.
|
||||||
|
/// </summary>
|
||||||
|
public Text Text { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the width of the column.
|
||||||
|
/// If <c>null</c>, the column will adapt to it's contents.
|
||||||
|
/// </summary>
|
||||||
|
public int? Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the padding of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Padding Padding { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether wrapping of
|
||||||
|
/// text within the column should be prevented.
|
||||||
|
/// </summary>
|
||||||
|
public bool NoWrap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alignment of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Alignment { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The table column text.</param>
|
||||||
|
public TableColumn(string text)
|
||||||
|
{
|
||||||
|
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
||||||
|
Width = null;
|
||||||
|
Padding = new Padding(1, 1);
|
||||||
|
NoWrap = false;
|
||||||
|
Alignment = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Spectre.Console.Composition;
|
using Spectre.Console.Composition;
|
||||||
using Spectre.Console.Internal;
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
@@ -12,11 +12,17 @@ namespace Spectre.Console
|
|||||||
/// Represents text with color and decorations.
|
/// Represents text with color and decorations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
||||||
|
[DebuggerDisplay("{_text,nq}")]
|
||||||
public sealed class Text : IRenderable
|
public sealed class Text : IRenderable
|
||||||
{
|
{
|
||||||
private readonly List<Span> _spans;
|
private readonly List<Span> _spans;
|
||||||
private string _text;
|
private string _text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the text alignment.
|
||||||
|
/// </summary>
|
||||||
|
public Justify Alignment { get; set; } = Justify.Left;
|
||||||
|
|
||||||
private sealed class Span
|
private sealed class Span
|
||||||
{
|
{
|
||||||
public int Start { get; }
|
public int Start { get; }
|
||||||
@@ -37,7 +43,7 @@ namespace Spectre.Console
|
|||||||
/// <param name="text">The text.</param>
|
/// <param name="text">The text.</param>
|
||||||
internal Text(string text)
|
internal Text(string text)
|
||||||
{
|
{
|
||||||
_text = text ?? throw new ArgumentNullException(nameof(text));
|
_text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
|
||||||
_spans = new List<Span>();
|
_spans = new List<Span>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,12 +56,26 @@ namespace Spectre.Console
|
|||||||
/// <param name="decoration">The text decoration.</param>
|
/// <param name="decoration">The text decoration.</param>
|
||||||
/// <returns>A <see cref="Text"/> instance.</returns>
|
/// <returns>A <see cref="Text"/> instance.</returns>
|
||||||
public static Text New(
|
public static Text New(
|
||||||
string text, Color? foreground = null, Color? background = null, Decoration? decoration = null)
|
string text,
|
||||||
|
Color? foreground = null,
|
||||||
|
Color? background = null,
|
||||||
|
Decoration? decoration = null)
|
||||||
{
|
{
|
||||||
var result = MarkupParser.Parse(text, new Style(foreground, background, decoration));
|
var result = MarkupParser.Parse(text, new Style(foreground, background, decoration));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the text alignment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="alignment">The text alignment.</param>
|
||||||
|
/// <returns>The same <see cref="Text"/> instance.</returns>
|
||||||
|
public Text WithAlignment(Justify alignment)
|
||||||
|
{
|
||||||
|
Alignment = alignment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends some text with the specified color and decorations.
|
/// Appends some text with the specified color and decorations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -96,26 +116,82 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Measure(Encoding encoding, int maxWidth)
|
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
var lines = _text.SplitLines();
|
if (string.IsNullOrEmpty(_text))
|
||||||
return lines.Max(x => x.CellLength(encoding));
|
{
|
||||||
|
return new Measurement(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Write some kind of tokenizer for this
|
||||||
|
var min = Segment.SplitLines(((IRenderable)this).Render(context, maxWidth))
|
||||||
|
.SelectMany(line => line.Select(segment => segment.Text.Length))
|
||||||
|
.Max();
|
||||||
|
|
||||||
|
var max = _text.SplitLines().Max(x => Cell.GetCellLength(context.Encoding, x));
|
||||||
|
|
||||||
|
return new Measurement(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||||
{
|
{
|
||||||
var result = new List<Segment>();
|
if (string.IsNullOrWhiteSpace(_text))
|
||||||
|
{
|
||||||
|
return Array.Empty<Segment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<Segment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<Segment>();
|
||||||
var segments = SplitLineBreaks(CreateSegments());
|
var segments = SplitLineBreaks(CreateSegments());
|
||||||
|
|
||||||
|
var justification = context.Justification ?? Alignment;
|
||||||
|
|
||||||
foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate())
|
foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate())
|
||||||
{
|
{
|
||||||
|
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));
|
||||||
|
|
||||||
|
if (length < width)
|
||||||
|
{
|
||||||
|
// Justify right side
|
||||||
|
if (justification == Justify.Right)
|
||||||
|
{
|
||||||
|
var diff = width - length;
|
||||||
|
result.Add(new Segment(new string(' ', diff)));
|
||||||
|
}
|
||||||
|
else if (justification == Justify.Center)
|
||||||
|
{
|
||||||
|
var diff = (width - length) / 2;
|
||||||
|
result.Add(new Segment(new string(' ', diff)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the line.
|
||||||
foreach (var segment in line)
|
foreach (var segment in line)
|
||||||
{
|
{
|
||||||
result.Add(segment.StripLineEndings());
|
result.Add(segment.StripLineEndings());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Justify left side
|
||||||
|
if (length < width)
|
||||||
|
{
|
||||||
|
if (justification == Justify.Center)
|
||||||
|
{
|
||||||
|
var diff = (width - length) / 2;
|
||||||
|
result.Add(new Segment(new string(' ', diff)));
|
||||||
|
|
||||||
|
var remainder = (width - length) % 2;
|
||||||
|
if (remainder != 0)
|
||||||
|
{
|
||||||
|
result.Add(new Segment(new string(' ', remainder)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!last)
|
if (!last)
|
||||||
{
|
{
|
||||||
result.Add(Segment.LineBreak());
|
result.Add(Segment.LineBreak());
|
||||||
@@ -129,11 +205,11 @@ namespace Spectre.Console
|
|||||||
{
|
{
|
||||||
// Creates individual segments of line breaks.
|
// Creates individual segments of line breaks.
|
||||||
var result = new List<Segment>();
|
var result = new List<Segment>();
|
||||||
var queue = new Queue<Segment>(segments);
|
var queue = new Stack<Segment>(segments.Reverse());
|
||||||
|
|
||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
var segment = queue.Dequeue();
|
var segment = queue.Pop();
|
||||||
|
|
||||||
var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase);
|
var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
@@ -149,7 +225,7 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Add(Segment.LineBreak());
|
result.Add(Segment.LineBreak());
|
||||||
queue.Enqueue(new Segment(second.Text.Substring(1), second.Style));
|
queue.Push(new Segment(second.Text.Substring(1), second.Style));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,10 +243,8 @@ namespace Spectre.Console
|
|||||||
|
|
||||||
// Create a span list.
|
// Create a span list.
|
||||||
var spans = new List<(int Offset, bool Leaving, int Style)>();
|
var spans = new List<(int Offset, bool Leaving, int Style)>();
|
||||||
spans.Add((0, false, 0));
|
|
||||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1)));
|
spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1)));
|
||||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1)));
|
spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1)));
|
||||||
spans.Add((_text.Length, true, 0));
|
|
||||||
spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList();
|
spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList();
|
||||||
|
|
||||||
// Keep track of applied styles using a stack
|
// Keep track of applied styles using a stack
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(renderable));
|
throw new ArgumentNullException(nameof(renderable));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var segment in renderable.Render(console.Encoding, console.Width))
|
var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole);
|
||||||
|
|
||||||
|
foreach (var segment in renderable.Render(options, console.Width))
|
||||||
{
|
{
|
||||||
if (!segment.Style.Equals(Style.Plain))
|
if (!segment.Style.Equals(Style.Plain))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ namespace Spectre.Console.Internal
|
|||||||
new Regex("bvterm"), // Bitvise SSH Client
|
new Regex("bvterm"), // Bitvise SSH Client
|
||||||
};
|
};
|
||||||
|
|
||||||
public static bool Detect(bool upgrade)
|
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade)
|
||||||
{
|
{
|
||||||
// Github action doesn't setup a correct PTY but supports ANSI.
|
// Github action doesn't setup a correct PTY but supports ANSI.
|
||||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
||||||
{
|
{
|
||||||
return true;
|
return (true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Running on Windows?
|
// Running on Windows?
|
||||||
@@ -47,10 +47,11 @@ namespace Spectre.Console.Internal
|
|||||||
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
|
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
|
||||||
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return (true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Windows.SupportsAnsi(upgrade);
|
var supportsAnsi = Windows.SupportsAnsi(upgrade, out var legacyConsole);
|
||||||
|
return (supportsAnsi, legacyConsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the terminal is of type ANSI/VT100/xterm compatible.
|
// Check if the terminal is of type ANSI/VT100/xterm compatible.
|
||||||
@@ -59,11 +60,11 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
if (_regexes.Any(regex => regex.IsMatch(term)))
|
if (_regexes.Any(regex => regex.IsMatch(term)))
|
||||||
{
|
{
|
||||||
return true;
|
return (true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return (false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
|
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
|
||||||
@@ -91,8 +92,10 @@ namespace Spectre.Console.Internal
|
|||||||
public static extern uint GetLastError();
|
public static extern uint GetLastError();
|
||||||
|
|
||||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||||
public static bool SupportsAnsi(bool upgrade)
|
public static bool SupportsAnsi(bool upgrade, out bool isLegacy)
|
||||||
{
|
{
|
||||||
|
isLegacy = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
|
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
@@ -104,6 +107,8 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
|
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
|
||||||
{
|
{
|
||||||
|
isLegacy = true;
|
||||||
|
|
||||||
if (!upgrade)
|
if (!upgrade)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ namespace Spectre.Console.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system)
|
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
|
||||||
{
|
{
|
||||||
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
||||||
_system = system;
|
_system = system;
|
||||||
|
|
||||||
Capabilities = new Capabilities(true, system);
|
Capabilities = new Capabilities(true, system, legacyConsole);
|
||||||
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
|
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
|
||||||
Foreground = Color.Default;
|
Foreground = Color.Default;
|
||||||
Background = Color.Default;
|
Background = Color.Default;
|
||||||
@@ -60,12 +60,19 @@ namespace Spectre.Console.Internal
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_out.Write(AnsiBuilder.GetAnsi(
|
var parts = text.NormalizeLineEndings().Split(new[] { '\n' });
|
||||||
_system,
|
foreach (var (_, _, last, part) in parts.Enumerate())
|
||||||
text.NormalizeLineEndings(native: true),
|
{
|
||||||
Decoration,
|
if (!string.IsNullOrEmpty(part))
|
||||||
Foreground,
|
{
|
||||||
Background));
|
_out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!last)
|
||||||
|
{
|
||||||
|
_out.Write(Environment.NewLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
if (supportsAnsi)
|
if (supportsAnsi)
|
||||||
{
|
{
|
||||||
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)$");
|
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)\\s*$");
|
||||||
var match = regex.Match(RuntimeInformation.OSDescription);
|
var match = regex.Match(RuntimeInformation.OSDescription);
|
||||||
if (match.Success && int.TryParse(match.Groups["major"].Value, out var major))
|
if (match.Success && int.TryParse(match.Groups["major"].Value, out var major))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
@@ -13,9 +14,41 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
var buffer = settings.Out ?? System.Console.Out;
|
var buffer = settings.Out ?? System.Console.Out;
|
||||||
|
|
||||||
var supportsAnsi = settings.Ansi == AnsiSupport.Detect
|
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
|
||||||
? AnsiDetector.Detect(true)
|
var legacyConsole = false;
|
||||||
: settings.Ansi == AnsiSupport.Yes;
|
|
||||||
|
if (settings.Ansi == AnsiSupport.Detect)
|
||||||
|
{
|
||||||
|
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(true);
|
||||||
|
|
||||||
|
// Check whether or not this is a legacy console from the existing instance (if any).
|
||||||
|
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
|
||||||
|
if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole)
|
||||||
|
{
|
||||||
|
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (buffer.IsStandardOut())
|
||||||
|
{
|
||||||
|
// Are we running on Windows?
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Not the first console we're creating?
|
||||||
|
if (AnsiConsole.Created)
|
||||||
|
{
|
||||||
|
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try detecting whether or not this
|
||||||
|
(_, legacyConsole) = AnsiDetector.Detect(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
||||||
? ColorSystemDetector.Detect(supportsAnsi)
|
? ColorSystemDetector.Detect(supportsAnsi)
|
||||||
@@ -23,13 +56,13 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
if (supportsAnsi)
|
if (supportsAnsi)
|
||||||
{
|
{
|
||||||
return new AnsiConsoleRenderer(buffer, colorSystem)
|
return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole)
|
||||||
{
|
{
|
||||||
Decoration = Decoration.None,
|
Decoration = Decoration.None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FallbackConsoleRenderer(buffer, colorSystem);
|
return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
internal static class EnumerableExtensions
|
internal static class EnumerableExtensions
|
||||||
{
|
{
|
||||||
|
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||||
|
{
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
action(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AnyTrue(this IEnumerable<bool> source)
|
||||||
|
{
|
||||||
|
return source.Any(b => b);
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
|
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
|
||||||
{
|
{
|
||||||
if (source is null)
|
if (source is null)
|
||||||
@@ -40,5 +53,18 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
return source.Select((value, index) => func(value, index));
|
return source.Select((value, index) => func(value, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
|
||||||
|
this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
|
||||||
|
{
|
||||||
|
return source.Zip(first, (first, second) => (first, second));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
|
||||||
|
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
||||||
|
{
|
||||||
|
return first.Zip(second, (a, b) => (a, b))
|
||||||
|
.Zip(third, (a, b) => (a.a, a.b, b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
public static string NormalizeLineEndings(this string text, bool native = false)
|
public static string NormalizeLineEndings(this string text, bool native = false)
|
||||||
{
|
{
|
||||||
|
if (text == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var normalized = text?.Replace("\r\n", "\n")
|
var normalized = text?.Replace("\r\n", "\n")
|
||||||
?.Replace("\r", string.Empty);
|
?.Replace("\r", string.Empty);
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace Spectre.Console.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system)
|
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
|
||||||
{
|
{
|
||||||
_out = @out;
|
_out = @out;
|
||||||
_system = system;
|
_system = system;
|
||||||
@@ -105,7 +105,7 @@ namespace Spectre.Console.Internal
|
|||||||
Encoding = Encoding.UTF8;
|
Encoding = Encoding.UTF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
Capabilities = new Capabilities(false, _system);
|
Capabilities = new Capabilities(false, _system, legacyConsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text)
|
public void Write(string text)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
@@ -35,7 +36,7 @@ namespace Spectre.Console.Internal
|
|||||||
else if (token.Kind == MarkupTokenKind.Text)
|
else if (token.Kind == MarkupTokenKind.Text)
|
||||||
{
|
{
|
||||||
// Get the effecive style.
|
// Get the effecive style.
|
||||||
var effectiveStyle = style.Combine(stack);
|
var effectiveStyle = style.Combine(stack.Reverse());
|
||||||
result.Append(token.Value, effectiveStyle);
|
result.Append(token.Value, effectiveStyle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ namespace Spectre.Console.Internal
|
|||||||
public static bool TryParse(string text, out Style style)
|
public static bool TryParse(string text, out Style style)
|
||||||
{
|
{
|
||||||
style = Parse(text, out var error);
|
style = Parse(text, out var error);
|
||||||
if (error != null)
|
return error == null;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Style Parse(string text, out string error)
|
private static Style Parse(string text, out string error)
|
||||||
|
|||||||
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||||
|
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/_ratio.py
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal
|
||||||
|
{
|
||||||
|
internal static class Ratio
|
||||||
|
{
|
||||||
|
public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
|
||||||
|
{
|
||||||
|
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
|
||||||
|
var totalRatio = ratios.Sum();
|
||||||
|
if (totalRatio <= 0)
|
||||||
|
{
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalRemaining = total;
|
||||||
|
var result = new List<int>();
|
||||||
|
|
||||||
|
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
||||||
|
{
|
||||||
|
if (ratio != 0 && totalRatio > 0)
|
||||||
|
{
|
||||||
|
var distributed = (int)Math.Min(maximum, Math.Round((double)(ratio * totalRemaining / totalRatio)));
|
||||||
|
result.Add(value - distributed);
|
||||||
|
totalRemaining -= distributed;
|
||||||
|
totalRatio -= ratio;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<int> Distribute(int total, List<int> ratios, List<int> minimums = null)
|
||||||
|
{
|
||||||
|
if (minimums != null)
|
||||||
|
{
|
||||||
|
ratios = ratios.Zip(minimums, (a, b) => (ratio: a, min: b)).Select(a => a.min > 0 ? a.ratio : 0).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalRatio = ratios.Sum();
|
||||||
|
Debug.Assert(totalRatio > 0, "Sum or ratios must be > 0");
|
||||||
|
|
||||||
|
var totalRemaining = total;
|
||||||
|
var distributedTotal = new List<int>();
|
||||||
|
|
||||||
|
if (minimums == null)
|
||||||
|
{
|
||||||
|
minimums = ratios.Select(_ => 0).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (ratio, minimum) in ratios.Zip(minimums, (a, b) => (a, b)))
|
||||||
|
{
|
||||||
|
var distributed = (totalRatio > 0)
|
||||||
|
? Math.Max(minimum, (int)Math.Ceiling(ratio * totalRemaining / (double)totalRatio))
|
||||||
|
: totalRemaining;
|
||||||
|
|
||||||
|
distributedTotal.Add(distributed);
|
||||||
|
totalRatio -= ratio;
|
||||||
|
totalRemaining -= distributed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distributedTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||||
|
<None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -21,6 +22,9 @@
|
|||||||
<Compile Update="ConsoleExtensions.*.cs">
|
<Compile Update="ConsoleExtensions.*.cs">
|
||||||
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="**/Table.*.cs">
|
||||||
|
<DependentUpon>**/Table.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user