mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d74fb909c | ||
|
|
5d132220ba | ||
|
|
a273f74758 | ||
|
|
717931f11c | ||
|
|
bcfc495843 | ||
|
|
9aa36c4cf0 | ||
|
|
22d4af4482 | ||
|
|
f4d1796e40 | ||
|
|
2dd0eb9f74 | ||
|
|
fa85216554 |
@@ -77,4 +77,7 @@ dotnet_diagnostic.CA1032.severity = none
|
|||||||
dotnet_diagnostic.CA1826.severity = none
|
dotnet_diagnostic.CA1826.severity = none
|
||||||
|
|
||||||
# RCS1079: Throwing of new NotImplementedException.
|
# RCS1079: Throwing of new NotImplementedException.
|
||||||
dotnet_diagnostic.RCS1079.severity = warning
|
dotnet_diagnostic.RCS1079.severity = warning
|
||||||
|
|
||||||
|
# RCS1057: Add empty line between declarations.
|
||||||
|
dotnet_diagnostic.RCS1057.severity = none
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
|
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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();
|
||||||
@@ -51,6 +52,7 @@ 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();
|
||||||
@@ -65,31 +67,38 @@ namespace Sample
|
|||||||
Text.New(
|
Text.New(
|
||||||
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
||||||
"So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" +
|
"So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" +
|
||||||
"😅", foreground: Color.White),
|
"😅", foreground: Color.White))
|
||||||
content: Justify.Center,
|
{ Alignment = Justify.Center, Border = BorderKind.Rounded })))
|
||||||
border: BorderKind.Rounded))),
|
{
|
||||||
border: BorderKind.Ascii));
|
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
|
// A normal, square table
|
||||||
var table = new Table();
|
var table = new Table();
|
||||||
@@ -143,7 +152,7 @@ namespace Sample
|
|||||||
AnsiConsole.Render(table);
|
AnsiConsole.Render(table);
|
||||||
|
|
||||||
// Render a table in some panels.
|
// Render a table in some panels.
|
||||||
AnsiConsole.Render(new Panel(new Panel(table, border: BorderKind.Ascii)));
|
AnsiConsole.Render(new Panel(new Panel(table) { Border = BorderKind.Ascii }) { Padding = new Padding(0, 0) });
|
||||||
|
|
||||||
// Draw another table
|
// Draw another table
|
||||||
table = new Table { Expand = false };
|
table = new Table { Expand = false };
|
||||||
|
|||||||
@@ -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; }
|
||||||
@@ -18,14 +18,19 @@ namespace Spectre.Console.Tests
|
|||||||
public Color Background { get; set; }
|
public Color Background { get; set; }
|
||||||
|
|
||||||
public StringWriter Writer { get; }
|
public StringWriter Writer { get; }
|
||||||
|
public string RawOutput => Writer.ToString();
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.0.0-beta0002" />
|
<PackageReference Include="Shouldly" Version="4.0.0-beta0002" />
|
||||||
<PackageReference Include="xunit" Version="2.4.0" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -10,13 +10,18 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
public sealed class TheGetBorderMethod
|
public sealed class TheGetBorderMethod
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(BorderKind.Ascii, typeof(AsciiBorder))]
|
[InlineData(BorderKind.None, false, typeof(NoBorder))]
|
||||||
[InlineData(BorderKind.Square, typeof(SquareBorder))]
|
[InlineData(BorderKind.Ascii, false, typeof(AsciiBorder))]
|
||||||
[InlineData(BorderKind.Rounded, typeof(RoundedBorder))]
|
[InlineData(BorderKind.Square, false, typeof(SquareBorder))]
|
||||||
public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, Type expected)
|
[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
|
// Given, When
|
||||||
var result = Border.GetBorder(kind);
|
var result = Border.GetBorder(kind, safe);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeOfType(expected);
|
result.ShouldBeOfType(expected);
|
||||||
@@ -26,7 +31,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
public void Should_Throw_If_Unknown_Border_Kind_Is_Specified()
|
public void Should_Throw_If_Unknown_Border_Kind_Is_Specified()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue));
|
var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue, false));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeOfType<InvalidOperationException>();
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
|
|||||||
@@ -6,6 +6,25 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
{
|
{
|
||||||
public sealed class GridTests
|
public sealed class GridTests
|
||||||
{
|
{
|
||||||
|
public sealed class TheAddColumnMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Rows_Are_Not_Empty()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn();
|
||||||
|
grid.AddRow("Hello World!");
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => grid.AddColumn());
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>()
|
||||||
|
.Message.ShouldBe("Cannot add new columns to grid with existing rows.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class TheAddRowMethod
|
public sealed class TheAddRowMethod
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -54,8 +73,32 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class TheAddEmptyRowMethod
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Add_Empty_Row()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumns(2);
|
||||||
|
grid.AddRow("Foo", "Bar");
|
||||||
|
grid.AddEmptyRow();
|
||||||
|
grid.AddRow("Qux", "Corgi");
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(grid);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(3);
|
||||||
|
console.Lines[0].ShouldBe("Foo Bar ");
|
||||||
|
console.Lines[1].ShouldBe(" ");
|
||||||
|
console.Lines[2].ShouldBe("Qux Corgi");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Render_Grid_With_No_Border_Correctly()
|
public void Should_Render_Grid_Correctly()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var console = new PlainConsole(width: 80);
|
var console = new PlainConsole(width: 80);
|
||||||
@@ -75,13 +118,80 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
console.Lines[1].ShouldBe("Grault Garply Fred ");
|
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_Use_Default_Padding()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 80);
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumns(3);
|
||||||
|
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_Explicit_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.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
|
||||||
|
grid.AddColumn(new GridColumn { Padding = new Padding(0, 3) });
|
||||||
|
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(" GraultGarplyFred ");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Render_Grid()
|
public void Should_Render_Grid()
|
||||||
{
|
{
|
||||||
var console = new PlainConsole(width: 80);
|
var console = new PlainConsole(width: 80);
|
||||||
var grid = new Grid();
|
var grid = new Grid();
|
||||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||||
grid.AddColumn();
|
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) });
|
||||||
grid.AddRow("[bold]Options[/]", string.Empty);
|
grid.AddRow("[bold]Options[/]", string.Empty);
|
||||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
|
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[/].");
|
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||||
@@ -91,10 +201,10 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(4);
|
console.Lines.Count.ShouldBe(4);
|
||||||
console.Lines[0].ShouldBe("Options ");
|
console.Lines[0].ShouldBe("Options ");
|
||||||
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||||
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||||
console.Lines[3].ShouldBe(" The default for most projects is Debug. ");
|
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);
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
result.ShouldBeOfType<ArgumentNullException>()
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
.ParamName.ShouldBe("column");
|
.ParamName.ShouldBe("column");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Rows_Are_Not_Empty()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var grid = new Table();
|
||||||
|
grid.AddColumn("Foo");
|
||||||
|
grid.AddRow("Hello World");
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => grid.AddColumn("Bar"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<InvalidOperationException>()
|
||||||
|
.Message.ShouldBe("Cannot add new columns to table with existing rows.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TheAddColumnsMethod
|
public sealed class TheAddColumnsMethod
|
||||||
@@ -32,7 +48,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
var table = new Table();
|
var table = new Table();
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = Record.Exception(() => table.AddColumns(null));
|
var result = Record.Exception(() => table.AddColumns((string[])null));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeOfType<ArgumentNullException>()
|
result.ShouldBeOfType<ArgumentNullException>()
|
||||||
@@ -88,29 +104,32 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
public sealed class TheAddEmptyRowMethod
|
||||||
public void Should_Measure_Table_Correctly()
|
|
||||||
{
|
{
|
||||||
// Given
|
[Fact]
|
||||||
var console = new PlainConsole(width: 80);
|
public void Should_Render_Table_Correctly()
|
||||||
var table = new Table();
|
{
|
||||||
table.AddColumns("Foo", "Bar", "Baz");
|
// Given
|
||||||
table.AddRow("Qux", "Corgi", "Waldo");
|
var console = new PlainConsole(width: 80);
|
||||||
table.AddRow("Grault", "Garply", "Fred");
|
var table = new Table();
|
||||||
|
table.AddColumns("Foo", "Bar", "Baz");
|
||||||
|
table.AddRow("Qux", "Corgi", "Waldo");
|
||||||
|
table.AddEmptyRow();
|
||||||
|
table.AddRow("Grault", "Garply", "Fred");
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Render(new Panel(table));
|
console.Render(table);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(8);
|
console.Lines.Count.ShouldBe(7);
|
||||||
console.Lines[0].ShouldBe("┌─────────────────────────────┐");
|
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||||
console.Lines[1].ShouldBe("│ ┌────────┬────────┬───────┐ │");
|
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||||
console.Lines[2].ShouldBe("│ │ Foo │ Bar │ Baz │ │");
|
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||||
console.Lines[3].ShouldBe("│ ├────────┼────────┼───────┤ │");
|
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||||
console.Lines[4].ShouldBe("│ │ Qux │ Corgi │ Waldo │ │");
|
console.Lines[4].ShouldBe("│ │ │ │");
|
||||||
console.Lines[5].ShouldBe("│ │ Grault │ Garply │ Fred │ │");
|
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
console.Lines[6].ShouldBe("│ └────────┴────────┴───────┘ │");
|
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||||
console.Lines[7].ShouldBe("└─────────────────────────────┘");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -136,6 +155,64 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
console.Lines[5].ShouldBe("└────────┴────────┴───────┘");
|
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]
|
[Fact]
|
||||||
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
||||||
{
|
{
|
||||||
@@ -221,9 +298,9 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
console.Lines.Count.ShouldBe(3);
|
console.Lines.Count.ShouldBe(3);
|
||||||
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
||||||
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
||||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -249,5 +326,49 @@ namespace Spectre.Console.Tests.Unit.Composition
|
|||||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||||
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
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("└─────┴─────┴────────┘");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,20 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
.ShouldBe("Hello World");
|
.ShouldBe("Hello World");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Write_Line_Breaks()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new PlainConsole(width: 5);
|
||||||
|
var text = Text.New("\n\n");
|
||||||
|
|
||||||
|
// When
|
||||||
|
fixture.Render(text);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fixture.RawOutput.ShouldBe("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width()
|
public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,14 +39,18 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("bold", Decoration.Bold)]
|
[InlineData("bold", Decoration.Bold)]
|
||||||
|
[InlineData("b", Decoration.Bold)]
|
||||||
[InlineData("dim", Decoration.Dim)]
|
[InlineData("dim", Decoration.Dim)]
|
||||||
|
[InlineData("i", Decoration.Italic)]
|
||||||
[InlineData("italic", Decoration.Italic)]
|
[InlineData("italic", Decoration.Italic)]
|
||||||
[InlineData("underline", Decoration.Underline)]
|
[InlineData("underline", Decoration.Underline)]
|
||||||
|
[InlineData("u", Decoration.Underline)]
|
||||||
[InlineData("invert", Decoration.Invert)]
|
[InlineData("invert", Decoration.Invert)]
|
||||||
[InlineData("conceal", Decoration.Conceal)]
|
[InlineData("conceal", Decoration.Conceal)]
|
||||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
[InlineData("slowblink", Decoration.SlowBlink)]
|
||||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
[InlineData("rapidblink", Decoration.RapidBlink)]
|
||||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
[InlineData("strikethrough", Decoration.Strikethrough)]
|
||||||
|
[InlineData("s", Decoration.Strikethrough)]
|
||||||
public void Should_Parse_Decoration(string text, Decoration decoration)
|
public void Should_Parse_Decoration(string text, Decoration decoration)
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
@@ -126,108 +130,83 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
result.ShouldBeOfType<InvalidOperationException>();
|
result.ShouldBeOfType<InvalidOperationException>();
|
||||||
result.Message.ShouldBe("Could not find color 'lol'.");
|
result.Message.ShouldBe("Could not find color 'lol'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("#FF0000 on #0000FF")]
|
||||||
|
[InlineData("#F00 on #00F")]
|
||||||
|
public void Should_Parse_Hex_Colors_Correctly(string style)
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Style.Parse(style);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Foreground.ShouldBe(Color.Red);
|
||||||
|
result.Background.ShouldBe(Color.Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("#", "Invalid hex color '#'.")]
|
||||||
|
[InlineData("#FF00FF00FF", "Invalid hex color '#FF00FF00FF'.")]
|
||||||
|
[InlineData("#FOO", "Invalid hex color '#FOO'. Could not find any recognizable digits.")]
|
||||||
|
public void Should_Return_Error_If_Hex_Color_Is_Invalid(string style, string expected)
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Record.Exception(() => Style.Parse(style));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldNotBeNull();
|
||||||
|
result.Message.ShouldBe(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("rgb(255,0,0) on rgb(0,0,255)")]
|
||||||
|
public void Should_Parse_Rgb_Colors_Correctly(string style)
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Style.Parse(style);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Foreground.ShouldBe(Color.Red);
|
||||||
|
result.Background.ShouldBe(Color.Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("rgb()", "Invalid RGB color 'rgb()'.")]
|
||||||
|
[InlineData("rgb(", "Invalid RGB color 'rgb('.")]
|
||||||
|
[InlineData("rgb(255)", "Invalid RGB color 'rgb(255)'.")]
|
||||||
|
[InlineData("rgb(255,255)", "Invalid RGB color 'rgb(255,255)'.")]
|
||||||
|
[InlineData("rgb(255,255,255", "Invalid RGB color 'rgb(255,255,255'.")]
|
||||||
|
[InlineData("rgb(A,B,C)", "Invalid RGB color 'rgb(A,B,C)'. Input string was not in a correct format.")]
|
||||||
|
public void Should_Return_Error_If_Rgb_Color_Is_Invalid(string style, string expected)
|
||||||
|
{
|
||||||
|
// Given, When
|
||||||
|
var result = Record.Exception(() => Style.Parse(style));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldNotBeNull();
|
||||||
|
result.Message.ShouldBe(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TheTryParseMethod
|
public sealed class TheTryParseMethod
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Default_Keyword_Should_Return_Default_Style()
|
public void Should_Return_True_If_Parsing_Succeeded()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("default", out var style);
|
var result = Style.TryParse("bold", out var style);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeTrue();
|
result.ShouldBeTrue();
|
||||||
style.ShouldNotBeNull();
|
style.ShouldNotBeNull();
|
||||||
style.Foreground.ShouldBe(Color.Default);
|
style.Decoration.ShouldBe(Decoration.Bold);
|
||||||
style.Background.ShouldBe(Color.Default);
|
|
||||||
style.Decoration.ShouldBe(Decoration.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("bold", Decoration.Bold)]
|
|
||||||
[InlineData("dim", Decoration.Dim)]
|
|
||||||
[InlineData("italic", Decoration.Italic)]
|
|
||||||
[InlineData("underline", Decoration.Underline)]
|
|
||||||
[InlineData("invert", Decoration.Invert)]
|
|
||||||
[InlineData("conceal", Decoration.Conceal)]
|
|
||||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
|
||||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
|
||||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
|
||||||
public void Should_Parse_Decoration(string text, Decoration decoration)
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse(text, out var style);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
style.ShouldNotBeNull();
|
|
||||||
style.Decoration.ShouldBe(decoration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Parse_Text_And_Decoration()
|
public void Should_Return_False_If_Parsing_Failed()
|
||||||
{
|
{
|
||||||
// Given, When
|
// Given, When
|
||||||
var result = Style.TryParse("bold underline blue on green", out var style);
|
var result = Style.TryParse("lol", out _);
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
style.ShouldNotBeNull();
|
|
||||||
style.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
|
|
||||||
style.Foreground.ShouldBe(Color.Blue);
|
|
||||||
style.Background.ShouldBe(Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse("default on green", out var style);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
style.ShouldNotBeNull();
|
|
||||||
style.Decoration.ShouldBe(Decoration.None);
|
|
||||||
style.Foreground.ShouldBe(Color.Default);
|
|
||||||
style.Background.ShouldBe(Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Throw_If_Foreground_Is_Set_Twice()
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse("green yellow", out _);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Throw_If_Background_Is_Set_Twice()
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse("green on blue yellow", out _);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse("bold lol", out _);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ShouldBeFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
|
|
||||||
{
|
|
||||||
// Given, When
|
|
||||||
var result = Style.TryParse("blue on lol", out _);
|
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ShouldBeFalse();
|
result.ShouldBeFalse();
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ namespace Spectre.Console
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the out buffer.
|
/// Gets or sets the out buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextWriter Out { get; set; }
|
public TextWriter? Out { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ namespace Spectre.Console
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool LegacyConsole { get; }
|
public bool LegacyConsole { get; }
|
||||||
|
|
||||||
internal Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole)
|
/// <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;
|
||||||
@@ -44,7 +50,7 @@ namespace Spectre.Console
|
|||||||
ColorSystem.Standard => "4 bits",
|
ColorSystem.Standard => "4 bits",
|
||||||
ColorSystem.EightBit => "8 bits",
|
ColorSystem.EightBit => "8 bits",
|
||||||
ColorSystem.TrueColor => "24 bits",
|
ColorSystem.TrueColor => "24 bits",
|
||||||
_ => "?"
|
_ => "?",
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return obj is Color color && Equals(color);
|
return obj is Color color && Equals(color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ namespace Spectre.Console.Composition
|
|||||||
{ BorderKind.Rounded, new RoundedBorder() },
|
{ BorderKind.Rounded, new RoundedBorder() },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind>
|
||||||
|
{
|
||||||
|
{ BorderKind.Rounded, BorderKind.Square },
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Border"/> class.
|
/// Initializes a new instance of the <see cref="Border"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,9 +37,15 @@ namespace Spectre.Console.Composition
|
|||||||
/// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>.
|
/// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="kind">The kind of border to get.</param>
|
/// <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>
|
/// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns>
|
||||||
public static Border GetBorder(BorderKind kind)
|
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))
|
if (!_borders.TryGetValue(kind, out var border))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Unknown border kind");
|
throw new InvalidOperationException("Unknown border kind");
|
||||||
@@ -46,15 +57,20 @@ namespace Spectre.Console.Composition
|
|||||||
private Dictionary<BorderPart, string> Initialize()
|
private Dictionary<BorderPart, string> Initialize()
|
||||||
{
|
{
|
||||||
var lookup = new Dictionary<BorderPart, string>();
|
var lookup = new Dictionary<BorderPart, string>();
|
||||||
foreach (BorderPart part in Enum.GetValues(typeof(BorderPart)))
|
foreach (BorderPart? part in Enum.GetValues(typeof(BorderPart)))
|
||||||
{
|
{
|
||||||
var text = GetBoxPart(part);
|
if (part == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = GetBoxPart(part.Value);
|
||||||
if (text.Length > 1)
|
if (text.Length > 1)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup.Add(part, GetBoxPart(part));
|
lookup.Add(part.Value, GetBoxPart(part.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return lookup;
|
return lookup;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
namespace Spectre.Console.Composition
|
namespace Spectre.Console.Composition
|
||||||
{
|
{
|
||||||
internal sealed class NoBorder : Border
|
/// <summary>
|
||||||
|
/// Represents an invisible border.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class NoBorder : Border
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
protected override string GetBoxPart(BorderPart part)
|
protected override string GetBoxPart(BorderPart part)
|
||||||
{
|
{
|
||||||
return " ";
|
return " ";
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Linq;
|
||||||
using Spectre.Console.Composition;
|
using Spectre.Console.Composition;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
{
|
{
|
||||||
@@ -21,19 +22,21 @@ namespace Spectre.Console
|
|||||||
{
|
{
|
||||||
Border = BorderKind.None,
|
Border = BorderKind.None,
|
||||||
ShowHeaders = false,
|
ShowHeaders = false,
|
||||||
|
IsGrid = true,
|
||||||
|
PadRightCell = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Measurement Measure(Encoding encoding, int maxWidth)
|
public Measurement Measure(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
return ((IRenderable)_table).Measure(encoding, maxWidth);
|
return ((IRenderable)_table).Measure(context, maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
public IEnumerable<Segment> Render(RenderContext context, int width)
|
||||||
{
|
{
|
||||||
return ((IRenderable)_table).Render(encoding, width);
|
return ((IRenderable)_table).Render(context, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,7 +44,7 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddColumn()
|
public void AddColumn()
|
||||||
{
|
{
|
||||||
_table.AddColumn(string.Empty);
|
AddColumn(new GridColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,15 +58,62 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(column));
|
throw new ArgumentNullException(nameof(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_table.RowCount > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot add new columns to grid with existing rows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only pad the most right cell if we've explicitly set a padding.
|
||||||
|
_table.PadRightCell = column.Padding != null;
|
||||||
|
|
||||||
_table.AddColumn(new TableColumn(string.Empty)
|
_table.AddColumn(new TableColumn(string.Empty)
|
||||||
{
|
{
|
||||||
Width = column.Width,
|
Width = column.Width,
|
||||||
NoWrap = column.NoWrap,
|
NoWrap = column.NoWrap,
|
||||||
LeftPadding = 0,
|
Padding = column.Padding ?? new Padding(0, 2),
|
||||||
RightPadding = 1,
|
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 an empty row to the grid.
|
||||||
|
/// </summary>
|
||||||
|
public void AddEmptyRow()
|
||||||
|
{
|
||||||
|
var columns = new string[_table.ColumnCount];
|
||||||
|
Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = string.Empty);
|
||||||
|
AddRow(columns);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new row to the grid.
|
/// Adds a new row to the grid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a grid column.
|
/// Represents a grid column.
|
||||||
@@ -16,5 +16,15 @@
|
|||||||
/// text within the column should be prevented.
|
/// text within the column should be prevented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NoWrap { get; set; }
|
public bool NoWrap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the padding of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Padding? Padding { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alignment of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Alignment { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 minimum and maximum width of the object.</returns>
|
/// <returns>The minimum and maximum width of the object.</returns>
|
||||||
Measurement 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Spectre.Console
|
|||||||
Right = 1,
|
Right = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Centered
|
/// Centered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Center = 2,
|
Center = 2,
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ namespace Spectre.Console.Composition
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return obj is Measurement measurement && Equals(measurement);
|
return obj is Measurement measurement && Equals(measurement);
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
@@ -10,119 +9,122 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Panel : IRenderable
|
public sealed class Panel : IRenderable
|
||||||
{
|
{
|
||||||
|
private const int EdgeWidth = 2;
|
||||||
|
|
||||||
private readonly IRenderable _child;
|
private readonly IRenderable _child;
|
||||||
private readonly bool _fit;
|
|
||||||
private readonly Justify _content;
|
/// <summary>
|
||||||
private readonly Border _border;
|
/// 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; }
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
|
||||||
|
/// <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>
|
|
||||||
/// <param name="border">The border to use.</param>
|
|
||||||
public Panel(
|
|
||||||
IRenderable child,
|
|
||||||
bool fit = false,
|
|
||||||
Justify content = Justify.Left,
|
|
||||||
BorderKind border = BorderKind.Square)
|
|
||||||
{
|
{
|
||||||
_child = child ?? throw new System.ArgumentNullException(nameof(child));
|
_child = content ?? throw new System.ArgumentNullException(nameof(content));
|
||||||
_fit = fit;
|
|
||||||
_content = content;
|
|
||||||
_border = Border.GetBorder(border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
Measurement IRenderable.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 new Measurement(childWidth.Min + 4, childWidth.Max + 4);
|
return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerable<Segment> IRenderable.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 paddingWidth = Padding.GetHorizontalPadding();
|
||||||
|
var childWidth = width - EdgeWidth - paddingWidth;
|
||||||
|
|
||||||
|
if (!Expand)
|
||||||
{
|
{
|
||||||
var measurement = _child.Measure(encoding, width - 2);
|
var measurement = _child.Measure(context, width - EdgeWidth - paddingWidth);
|
||||||
childWidth = measurement.Max;
|
childWidth = measurement.Max;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<Segment>();
|
var panelWidth = childWidth + paddingWidth;
|
||||||
var panelWidth = childWidth + 2;
|
|
||||||
|
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopLeft)));
|
// Panel top
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop, panelWidth)));
|
var result = new List<Segment>
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopRight)));
|
{
|
||||||
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(_border.GetPart(BorderPart.CellLeft)));
|
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||||
result.Add(new Segment(" ")); // Left padding
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
// Justify right side
|
var diff = childWidth - length;
|
||||||
if (_content == Justify.Right)
|
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Justify left side
|
|
||||||
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
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.CellRight)));
|
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(_border.GetPart(BorderPart.FooterBottomLeft)));
|
// Panel bottom
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom, panelWidth)));
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomRight)));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,12 @@ namespace Spectre.Console.Composition
|
|||||||
|
|
||||||
private Segment(string text, Style style, bool lineBreak)
|
private Segment(string text, Style style, bool lineBreak)
|
||||||
{
|
{
|
||||||
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
|
if (text is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
Text = text.NormalizeLineEndings();
|
||||||
Style = style;
|
Style = style;
|
||||||
IsLineBreak = lineBreak;
|
IsLineBreak = lineBreak;
|
||||||
}
|
}
|
||||||
@@ -89,7 +94,7 @@ namespace Spectre.Console.Composition
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offset">The offset where to split the segment.</param>
|
/// <param name="offset">The offset where to split the segment.</param>
|
||||||
/// <returns>One or two new segments representing the split.</returns>
|
/// <returns>One or two new segments representing the split.</returns>
|
||||||
public (Segment First, Segment Second) Split(int offset)
|
public (Segment First, Segment? Second) Split(int offset)
|
||||||
{
|
{
|
||||||
if (offset < 0)
|
if (offset < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
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;
|
||||||
using Spectre.Console.Internal;
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
@@ -12,57 +11,18 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class Table
|
public sealed partial class Table
|
||||||
{
|
{
|
||||||
|
private const int EdgeCount = 2;
|
||||||
|
|
||||||
// Calculate the widths of each column, including padding, not including borders.
|
// Calculate the widths of each column, including padding, not including borders.
|
||||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||||
private List<int> CalculateColumnWidths(Encoding encoding, int maxWidth)
|
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
|
||||||
{
|
{
|
||||||
var width_ranges = _columns.Select(column => MeasureColumn(column, encoding, maxWidth));
|
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth));
|
||||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||||
|
|
||||||
var tableWidth = widths.Sum();
|
var tableWidth = widths.Sum();
|
||||||
|
|
||||||
if (ShouldExpand())
|
|
||||||
{
|
|
||||||
var ratios = _columns.Select(c => c.Ratio ?? 0).ToList();
|
|
||||||
if (ratios.Any(r => r != 0))
|
|
||||||
{
|
|
||||||
var fixedWidths = new List<int>();
|
|
||||||
foreach (var (range, column) in width_ranges.Zip(_columns, (a, b) => (a, b)))
|
|
||||||
{
|
|
||||||
fixedWidths.Add(column.IsFlexible() ? 0 : range.Max);
|
|
||||||
}
|
|
||||||
|
|
||||||
var flexMinimum = new List<int>();
|
|
||||||
foreach (var column in _columns)
|
|
||||||
{
|
|
||||||
if (column.IsFlexible())
|
|
||||||
{
|
|
||||||
flexMinimum.Add(column.Width ?? 1 + column.GetPadding());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
flexMinimum.Add(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var flexibleWidth = maxWidth - fixedWidths.Sum();
|
|
||||||
var flexWidths = Ratio.Distribute(flexibleWidth, ratios, flexMinimum);
|
|
||||||
|
|
||||||
var flexWidthsIterator = flexWidths.GetEnumerator();
|
|
||||||
foreach (var (index, _, _, column) in _columns.Enumerate())
|
|
||||||
{
|
|
||||||
if (column.IsFlexible())
|
|
||||||
{
|
|
||||||
flexWidthsIterator.MoveNext();
|
|
||||||
widths[index] = fixedWidths[index] + flexWidthsIterator.Current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tableWidth = widths.Sum();
|
|
||||||
|
|
||||||
if (tableWidth > maxWidth)
|
if (tableWidth > maxWidth)
|
||||||
{
|
{
|
||||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||||
@@ -73,7 +33,7 @@ namespace Spectre.Console
|
|||||||
if (tableWidth > maxWidth)
|
if (tableWidth > maxWidth)
|
||||||
{
|
{
|
||||||
var excessWidth = tableWidth - maxWidth;
|
var excessWidth = tableWidth - maxWidth;
|
||||||
widths = Ratio.Reduce(excessWidth, widths.Select(w => 1).ToList(), widths, widths);
|
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
|
||||||
tableWidth = widths.Sum();
|
tableWidth = widths.Sum();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,26 +57,17 @@ namespace Spectre.Console
|
|||||||
|
|
||||||
if (wrappable.AnyTrue())
|
if (wrappable.AnyTrue())
|
||||||
{
|
{
|
||||||
while (totalWidth > 0 && excessWidth > 0)
|
while (totalWidth != 0 && excessWidth > 0)
|
||||||
{
|
{
|
||||||
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, isWrappable: second))
|
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
|
||||||
.Where(x => x.isWrappable)
|
.Where(x => x.allowWrap)
|
||||||
.Max(x => x.width);
|
.Max(x => x.width);
|
||||||
|
|
||||||
var secondMaxColumn = widths.Zip(wrappable, (width, isWrappable) => isWrappable && width != maxColumn ? maxColumn : 0).Max();
|
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max();
|
||||||
var columnDifference = maxColumn - secondMaxColumn;
|
var columnDifference = maxColumn - secondMaxColumn;
|
||||||
|
|
||||||
var ratios = widths.Zip(wrappable, (width, allowWrap) =>
|
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
|
||||||
{
|
if (!ratios.Any(x => x != 0) || columnDifference == 0)
|
||||||
if (width == maxColumn && allowWrap)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
if (!ratios.Any(x => x > 0) || columnDifference == 0)
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -132,12 +83,13 @@ namespace Spectre.Console
|
|||||||
return widths;
|
return widths;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (int Min, int Max) MeasureColumn(TableColumn column, Encoding encoding, int maxWidth)
|
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
|
||||||
{
|
{
|
||||||
|
var padding = column.Padding.GetHorizontalPadding();
|
||||||
|
|
||||||
// Predetermined width?
|
// Predetermined width?
|
||||||
if (column.Width != null)
|
if (column.Width != null)
|
||||||
{
|
{
|
||||||
var padding = column.GetPadding();
|
|
||||||
return (column.Width.Value + padding, column.Width.Value + padding);
|
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,23 +98,28 @@ namespace Spectre.Console
|
|||||||
|
|
||||||
var minWidths = new List<int>();
|
var minWidths = new List<int>();
|
||||||
var maxWidths = 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)
|
foreach (var row in rows)
|
||||||
{
|
{
|
||||||
var measure = ((IRenderable)row).Measure(encoding, maxWidth);
|
measure = ((IRenderable)row).Measure(options, maxWidth);
|
||||||
minWidths.Add(measure.Min);
|
minWidths.Add(measure.Min);
|
||||||
maxWidths.Add(measure.Max);
|
maxWidths.Add(measure.Max);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (minWidths.Count > 0 ? minWidths.Max() : 1,
|
return (minWidths.Count > 0 ? minWidths.Max() : padding,
|
||||||
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetExtraWidth(bool includePadding)
|
private int GetExtraWidth(bool includePadding)
|
||||||
{
|
{
|
||||||
var edges = 2;
|
|
||||||
var separators = _columns.Count - 1;
|
var separators = _columns.Count - 1;
|
||||||
var padding = includePadding ? _columns.Select(x => x.GetPadding()).Sum() : 0;
|
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||||
return separators + edges + padding;
|
return separators + EdgeCount + padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
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;
|
||||||
using Spectre.Console.Internal;
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
@@ -40,12 +39,27 @@ namespace Spectre.Console
|
|||||||
/// fit the available space. If <c>false</c>, the table width will be
|
/// fit the available space. If <c>false</c>, the table width will be
|
||||||
/// auto calculated. Defaults to <c>false</c>.
|
/// auto calculated. Defaults to <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Expand { get; set; } = false;
|
public bool Expand { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the width of the table.
|
/// Gets or sets the width of the table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? Width { get; set; } = null;
|
public int? Width { get; set; }
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
// Whether this is a grid or not.
|
||||||
|
internal bool IsGrid { get; set; }
|
||||||
|
|
||||||
|
// Whether or not the most right cell should be padded.
|
||||||
|
// This is almost always the case, unless we're rendering
|
||||||
|
// a grid without explicit padding in the last cell.
|
||||||
|
internal bool PadRightCell { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Table"/> class.
|
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||||
@@ -67,7 +81,7 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(column));
|
throw new ArgumentNullException(nameof(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
_columns.Add(new TableColumn(column));
|
AddColumn(new TableColumn(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -81,6 +95,11 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(column));
|
throw new ArgumentNullException(nameof(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_rows.Count > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot add new columns to table with existing rows.");
|
||||||
|
}
|
||||||
|
|
||||||
_columns.Add(column);
|
_columns.Add(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +114,37 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(columns));
|
throw new ArgumentNullException(nameof(columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
AddColumn(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
AddColumn(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an empty row to the table.
|
||||||
|
/// </summary>
|
||||||
|
public void AddEmptyRow()
|
||||||
|
{
|
||||||
|
var columns = new string[ColumnCount];
|
||||||
|
Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = string.Empty);
|
||||||
|
AddRow(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,8 +172,13 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
|
if (context is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
if (Width != null)
|
if (Width != null)
|
||||||
{
|
{
|
||||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||||
@@ -132,7 +186,7 @@ namespace Spectre.Console
|
|||||||
|
|
||||||
maxWidth -= GetExtraWidth(includePadding: true);
|
maxWidth -= GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
var measurements = _columns.Select(column => MeasureColumn(column, encoding, maxWidth)).ToList();
|
var measurements = _columns.Select(column => MeasureColumn(column, context, maxWidth)).ToList();
|
||||||
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||||
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
@@ -140,12 +194,18 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||||
{
|
{
|
||||||
var border = Composition.Border.GetBorder(Border);
|
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 showBorder = Border != BorderKind.None;
|
||||||
var hideBorder = Border == BorderKind.None;
|
var hideBorder = Border == BorderKind.None;
|
||||||
|
var hasRows = _rows.Count > 0;
|
||||||
|
|
||||||
var maxWidth = width;
|
var maxWidth = width;
|
||||||
if (Width != null)
|
if (Width != null)
|
||||||
@@ -156,10 +216,10 @@ namespace Spectre.Console
|
|||||||
maxWidth -= GetExtraWidth(includePadding: true);
|
maxWidth -= GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
// Calculate the column and table widths
|
// Calculate the column and table widths
|
||||||
var columnWidths = CalculateColumnWidths(encoding, maxWidth);
|
var columnWidths = CalculateColumnWidths(context, maxWidth);
|
||||||
|
|
||||||
// Update the table width.
|
// Update the table width.
|
||||||
width = columnWidths.Sum() + GetExtraWidth(includePadding: false);
|
width = columnWidths.Sum() + GetExtraWidth(includePadding: true);
|
||||||
|
|
||||||
var rows = new List<List<Text>>();
|
var rows = new List<List<Text>>();
|
||||||
if (ShowHeaders)
|
if (ShowHeaders)
|
||||||
@@ -179,9 +239,12 @@ namespace Spectre.Console
|
|||||||
|
|
||||||
// Get the list of cells for the row and calculate the cell height
|
// Get the list of cells for the row and calculate the cell height
|
||||||
var cells = new List<List<SegmentLine>>();
|
var cells = new List<List<SegmentLine>>();
|
||||||
foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s)))
|
foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
|
||||||
{
|
{
|
||||||
var lines = Segment.SplitLines(((IRenderable)cell).Render(encoding, rowWidth));
|
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);
|
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||||
cells.Add(lines);
|
cells.Add(lines);
|
||||||
}
|
}
|
||||||
@@ -192,9 +255,11 @@ namespace Spectre.Console
|
|||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||||
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
{
|
{
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Left padding
|
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, columnWidth)));
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Right padding
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding
|
||||||
|
|
||||||
if (!lastColumn)
|
if (!lastColumn)
|
||||||
{
|
{
|
||||||
@@ -221,9 +286,9 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pad column on left side.
|
// Pad column on left side.
|
||||||
if (showBorder)
|
if (showBorder || IsGrid)
|
||||||
{
|
{
|
||||||
var leftPadding = _columns[cellIndex].LeftPadding;
|
var leftPadding = _columns[cellIndex].Padding.Left;
|
||||||
if (leftPadding > 0)
|
if (leftPadding > 0)
|
||||||
{
|
{
|
||||||
result.Add(new Segment(new string(' ', leftPadding)));
|
result.Add(new Segment(new string(' ', leftPadding)));
|
||||||
@@ -234,16 +299,16 @@ namespace Spectre.Console
|
|||||||
result.AddRange(cell[cellRowIndex]);
|
result.AddRange(cell[cellRowIndex]);
|
||||||
|
|
||||||
// Pad cell content right
|
// Pad cell content right
|
||||||
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(encoding));
|
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding));
|
||||||
if (length < columnWidths[cellIndex])
|
if (length < columnWidths[cellIndex])
|
||||||
{
|
{
|
||||||
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
|
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad column on the right side
|
// Pad column on the right side
|
||||||
if (showBorder || (hideBorder && !lastCell))
|
if (showBorder || (hideBorder && !lastCell) || (hideBorder && lastCell && IsGrid && PadRightCell))
|
||||||
{
|
{
|
||||||
var rightPadding = _columns[cellIndex].RightPadding;
|
var rightPadding = _columns[cellIndex].Padding.Right;
|
||||||
if (rightPadding > 0)
|
if (rightPadding > 0)
|
||||||
{
|
{
|
||||||
result.Add(new Segment(new string(' ', rightPadding)));
|
result.Add(new Segment(new string(' ', rightPadding)));
|
||||||
@@ -255,7 +320,7 @@ namespace Spectre.Console
|
|||||||
// Add right column edge
|
// Add right column edge
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||||
}
|
}
|
||||||
else if (showBorder || (hideBorder && !lastCell))
|
else if (showBorder)
|
||||||
{
|
{
|
||||||
// Add column separator
|
// Add column separator
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||||
@@ -265,15 +330,17 @@ namespace Spectre.Console
|
|||||||
result.Add(Segment.LineBreak());
|
result.Add(Segment.LineBreak());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show bottom of header?
|
// Show header separator?
|
||||||
if (firstRow && showBorder && ShowHeaders)
|
if (firstRow && showBorder && ShowHeaders && hasRows)
|
||||||
{
|
{
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
{
|
{
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Left padding
|
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, columnWidth)));
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Right padding
|
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding
|
||||||
|
|
||||||
if (!lastColumn)
|
if (!lastColumn)
|
||||||
{
|
{
|
||||||
@@ -291,9 +358,11 @@ namespace Spectre.Console
|
|||||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||||
{
|
{
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
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, columnWidth)));
|
||||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding
|
||||||
|
|
||||||
if (!lastColumn)
|
if (!lastColumn)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,20 +19,9 @@ namespace Spectre.Console
|
|||||||
public int? Width { get; set; }
|
public int? Width { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the left padding.
|
/// Gets or sets the padding of the column.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int LeftPadding { get; set; }
|
public Padding Padding { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the right padding.
|
|
||||||
/// </summary>
|
|
||||||
public int RightPadding { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the ratio to use when calculating column width.
|
|
||||||
/// If <c>null</c>, the column will adapt to it's contents.
|
|
||||||
/// </summary>
|
|
||||||
public int? Ratio { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether wrapping of
|
/// Gets or sets a value indicating whether wrapping of
|
||||||
@@ -40,6 +29,11 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NoWrap { get; set; }
|
public bool NoWrap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the alignment of the column.
|
||||||
|
/// </summary>
|
||||||
|
public Justify? Alignment { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,20 +42,9 @@ namespace Spectre.Console
|
|||||||
{
|
{
|
||||||
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
||||||
Width = null;
|
Width = null;
|
||||||
LeftPadding = 1;
|
Padding = new Padding(1, 1);
|
||||||
RightPadding = 1;
|
|
||||||
Ratio = null;
|
|
||||||
NoWrap = false;
|
NoWrap = false;
|
||||||
}
|
Alignment = null;
|
||||||
|
|
||||||
internal int GetPadding()
|
|
||||||
{
|
|
||||||
return LeftPadding + RightPadding;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsFlexible()
|
|
||||||
{
|
|
||||||
return Width == null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
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;
|
||||||
|
|
||||||
@@ -19,6 +18,11 @@ namespace Spectre.Console
|
|||||||
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; }
|
||||||
@@ -39,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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,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>
|
||||||
@@ -98,24 +116,32 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||||
{
|
{
|
||||||
var lines = Segment.SplitLines(((IRenderable)this).Render(encoding, maxWidth));
|
if (string.IsNullOrEmpty(_text))
|
||||||
if (lines.Count == 0)
|
|
||||||
{
|
{
|
||||||
return new Measurement(0, maxWidth);
|
return new Measurement(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var max = lines.Max(line => line.Length);
|
// TODO: Write some kind of tokenizer for this
|
||||||
var min = lines.SelectMany(line => line.Select(segment => segment.Text.Length)).Max();
|
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);
|
return new Measurement(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_text))
|
if (string.IsNullOrEmpty(_text))
|
||||||
|
{
|
||||||
|
return Array.Empty<Segment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<Segment>();
|
return Array.Empty<Segment>();
|
||||||
}
|
}
|
||||||
@@ -123,14 +149,50 @@ namespace Spectre.Console
|
|||||||
var result = new List<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());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!last)
|
// 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 || line.Count == 0)
|
||||||
{
|
{
|
||||||
result.Add(Segment.LineBreak());
|
result.Add(Segment.LineBreak());
|
||||||
}
|
}
|
||||||
@@ -139,7 +201,7 @@ namespace Spectre.Console
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments)
|
private static IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments)
|
||||||
{
|
{
|
||||||
// Creates individual segments of line breaks.
|
// Creates individual segments of line breaks.
|
||||||
var result = new List<Segment>();
|
var result = new List<Segment>();
|
||||||
@@ -152,7 +214,10 @@ namespace Spectre.Console
|
|||||||
var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase);
|
var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
{
|
{
|
||||||
result.Add(segment);
|
if (!string.IsNullOrEmpty(segment.Text))
|
||||||
|
{
|
||||||
|
result.Add(segment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -163,7 +228,11 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Add(Segment.LineBreak());
|
result.Add(Segment.LineBreak());
|
||||||
queue.Push(new Segment(second.Text.Substring(1), second.Style));
|
|
||||||
|
if (second != null)
|
||||||
|
{
|
||||||
|
queue.Push(new Segment(second.Text.Substring(1), second.Style));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Spectre.Console.Internal
|
|||||||
return ColorPalette.EightBit[number];
|
return ColorPalette.EightBit[number];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetName(int number)
|
public static string? GetName(int number)
|
||||||
{
|
{
|
||||||
_nameLookup.TryGetValue(number, out var name);
|
_nameLookup.TryGetValue(number, out var name);
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@@ -15,14 +15,18 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
{ "none", Decoration.None },
|
{ "none", Decoration.None },
|
||||||
{ "bold", Decoration.Bold },
|
{ "bold", Decoration.Bold },
|
||||||
|
{ "b", Decoration.Bold },
|
||||||
{ "dim", Decoration.Dim },
|
{ "dim", Decoration.Dim },
|
||||||
{ "italic", Decoration.Italic },
|
{ "italic", Decoration.Italic },
|
||||||
|
{ "i", Decoration.Italic },
|
||||||
{ "underline", Decoration.Underline },
|
{ "underline", Decoration.Underline },
|
||||||
|
{ "u", Decoration.Underline },
|
||||||
{ "invert", Decoration.Invert },
|
{ "invert", Decoration.Invert },
|
||||||
{ "conceal", Decoration.Conceal },
|
{ "conceal", Decoration.Conceal },
|
||||||
{ "slowblink", Decoration.SlowBlink },
|
{ "slowblink", Decoration.SlowBlink },
|
||||||
{ "rapidblink", Decoration.RapidBlink },
|
{ "rapidblink", Decoration.RapidBlink },
|
||||||
{ "strikethrough", Decoration.Strikethrough },
|
{ "strikethrough", Decoration.Strikethrough },
|
||||||
|
{ "s", Decoration.Strikethrough },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
public static string NormalizeLineEndings(this string text, bool native = false)
|
public static string NormalizeLineEndings(this string text, bool native = false)
|
||||||
{
|
{
|
||||||
var normalized = text?.Replace("\r\n", "\n")
|
text ??= string.Empty;
|
||||||
?.Replace("\r", string.Empty);
|
|
||||||
|
|
||||||
|
var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty;
|
||||||
if (native && !_alreadyNormalized)
|
if (native && !_alreadyNormalized)
|
||||||
{
|
{
|
||||||
normalized = normalized.Replace("\n", Environment.NewLine);
|
normalized = normalized.Replace("\n", Environment.NewLine);
|
||||||
@@ -30,7 +30,8 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
public static string[] SplitLines(this string text)
|
public static string[] SplitLines(this string text)
|
||||||
{
|
{
|
||||||
return text.NormalizeLineEndings().Split(new[] { '\n' }, StringSplitOptions.None);
|
var result = text?.NormalizeLineEndings()?.Split(new[] { '\n' }, StringSplitOptions.None);
|
||||||
|
return result ?? Array.Empty<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
internal static class MarkupParser
|
internal static class MarkupParser
|
||||||
{
|
{
|
||||||
public static Text Parse(string text, Style style = null)
|
public static Text Parse(string text, Style? style = null)
|
||||||
{
|
{
|
||||||
style ??= Style.Plain;
|
style ??= Style.Plain;
|
||||||
|
|
||||||
@@ -18,6 +18,10 @@ namespace Spectre.Console.Internal
|
|||||||
while (tokenizer.MoveNext())
|
while (tokenizer.MoveNext())
|
||||||
{
|
{
|
||||||
var token = tokenizer.Current;
|
var token = tokenizer.Current;
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (token.Kind == MarkupTokenKind.Open)
|
if (token.Kind == MarkupTokenKind.Open)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
private readonly StringBuffer _reader;
|
private readonly StringBuffer _reader;
|
||||||
|
|
||||||
public MarkupToken Current { get; private set; }
|
public MarkupToken? Current { get; private set; }
|
||||||
|
|
||||||
public MarkupTokenizer(string text)
|
public MarkupTokenizer(string text)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
internal sealed class StringBuffer : IDisposable
|
internal sealed class StringBuffer : IDisposable
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")]
|
||||||
private readonly StringReader _reader;
|
private readonly StringReader _reader;
|
||||||
private readonly int _length;
|
private readonly int _length;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
@@ -12,16 +14,23 @@ namespace Spectre.Console.Internal
|
|||||||
throw new InvalidOperationException(error);
|
throw new InvalidOperationException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (style == null)
|
||||||
|
{
|
||||||
|
// This should not happen, but we need to please the compiler
|
||||||
|
// which cannot know that style isn't null here.
|
||||||
|
throw new InvalidOperationException("Could not parse style.");
|
||||||
|
}
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
return error == null;
|
return error == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Style Parse(string text, out string error)
|
private static Style? Parse(string text, out string? error)
|
||||||
{
|
{
|
||||||
var effectiveDecoration = (Decoration?)null;
|
var effectiveDecoration = (Decoration?)null;
|
||||||
var effectiveForeground = (Color?)null;
|
var effectiveForeground = (Color?)null;
|
||||||
@@ -57,16 +66,30 @@ namespace Spectre.Console.Internal
|
|||||||
var color = ColorTable.GetColor(part);
|
var color = ColorTable.GetColor(part);
|
||||||
if (color == null)
|
if (color == null)
|
||||||
{
|
{
|
||||||
if (!foreground)
|
if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
error = $"Could not find color '{part}'.";
|
color = ParseHexColor(part, out error);
|
||||||
|
if (!string.IsNullOrWhiteSpace(error))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
color = ParseRgbColor(part, out error);
|
||||||
|
if (!string.IsNullOrWhiteSpace(error))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
error = $"Could not find color or style '{part}'.";
|
error = !foreground
|
||||||
}
|
? $"Could not find color '{part}'."
|
||||||
|
: $"Could not find color or style '{part}'.";
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foreground)
|
if (foreground)
|
||||||
@@ -95,5 +118,82 @@ namespace Spectre.Console.Internal
|
|||||||
error = null;
|
error = null;
|
||||||
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration);
|
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||||
|
private static Color? ParseHexColor(string hex, out string? error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
hex ??= string.Empty;
|
||||||
|
hex = hex.Replace("#", string.Empty).Trim();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(hex))
|
||||||
|
{
|
||||||
|
if (hex.Length == 6)
|
||||||
|
{
|
||||||
|
return new Color(
|
||||||
|
(byte)Convert.ToUInt32(hex.Substring(0, 2), 16),
|
||||||
|
(byte)Convert.ToUInt32(hex.Substring(2, 2), 16),
|
||||||
|
(byte)Convert.ToUInt32(hex.Substring(4, 2), 16));
|
||||||
|
}
|
||||||
|
else if (hex.Length == 3)
|
||||||
|
{
|
||||||
|
return new Color(
|
||||||
|
(byte)Convert.ToUInt32(new string(hex[0], 2), 16),
|
||||||
|
(byte)Convert.ToUInt32(new string(hex[1], 2), 16),
|
||||||
|
(byte)Convert.ToUInt32(new string(hex[2], 2), 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = $"Invalid hex color '#{hex}'. {ex.Message}";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = $"Invalid hex color '#{hex}'.";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||||
|
private static Color? ParseRgbColor(string rgb, out string? error)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
var normalized = rgb ?? string.Empty;
|
||||||
|
if (normalized.Length >= 3)
|
||||||
|
{
|
||||||
|
// Trim parenthesises
|
||||||
|
normalized = normalized.Substring(3).Trim();
|
||||||
|
|
||||||
|
if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
normalized = normalized.Trim('(').Trim(')');
|
||||||
|
|
||||||
|
var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (parts.Length == 3)
|
||||||
|
{
|
||||||
|
return new Color(
|
||||||
|
(byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture),
|
||||||
|
(byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture),
|
||||||
|
(byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = $"Invalid RGB color '{rgb}'. {ex.Message}";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = $"Invalid RGB color '{rgb}'.";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
|
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
|
||||||
var totalRatio = ratios.Sum();
|
var totalRatio = ratios.Sum();
|
||||||
if (totalRatio == 0)
|
if (totalRatio <= 0)
|
||||||
{
|
{
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@@ -24,9 +24,9 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
||||||
{
|
{
|
||||||
if (ratio > 0 && totalRatio > 0)
|
if (ratio != 0 && totalRatio > 0)
|
||||||
{
|
{
|
||||||
var distributed = (int)Math.Min(maximum, Math.Round(ratio * totalRemaining / (double)totalRatio));
|
var distributed = (int)Math.Min(maximum, Math.Round((double)(ratio * totalRemaining / totalRatio)));
|
||||||
result.Add(value - distributed);
|
result.Add(value - distributed);
|
||||||
totalRemaining -= distributed;
|
totalRemaining -= distributed;
|
||||||
totalRatio -= ratio;
|
totalRatio -= ratio;
|
||||||
@@ -40,7 +40,7 @@ namespace Spectre.Console.Internal
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<int> Distribute(int total, List<int> ratios, List<int> minimums = null)
|
public static List<int> Distribute(int total, List<int> ratios, List<int>? minimums = null)
|
||||||
{
|
{
|
||||||
if (minimums != null)
|
if (minimums != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -9,27 +10,21 @@
|
|||||||
<None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
<None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="**/ColorPalette.*.cs">
|
|
||||||
<DependentUpon>**/ColorPalette.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Color.*.cs">
|
|
||||||
<DependentUpon>Color.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="AnsiConsole.*.cs">
|
|
||||||
<DependentUpon>AnsiConsole.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="ConsoleExtensions.*.cs">
|
|
||||||
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="**/Table.*.cs">
|
|
||||||
<DependentUpon>**/Table.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
|
||||||
|
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
|
||||||
|
<PackageReference Include="Nullable" Version="1.2.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion>
|
||||||
|
<GenerateNullableAttributes>False</GenerateNullableAttributes>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Spectre.Console
|
|||||||
/// if the conversion succeeded, or <c>null</c> if the conversion failed.
|
/// if the conversion succeeded, or <c>null</c> if the conversion failed.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns>
|
||||||
public static bool TryParse(string text, out Style result)
|
public static bool TryParse(string text, out Style? result)
|
||||||
{
|
{
|
||||||
return StyleParser.TryParse(text, out result);
|
return StyleParser.TryParse(text, out result);
|
||||||
}
|
}
|
||||||
@@ -113,13 +113,13 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return Equals(obj as Style);
|
return Equals(obj as Style);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Equals(Style other)
|
public bool Equals(Style? other)
|
||||||
{
|
{
|
||||||
if (other == null)
|
if (other == null)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user