mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d74fb909c | ||
|
|
5d132220ba | ||
|
|
a273f74758 | ||
|
|
717931f11c | ||
|
|
bcfc495843 |
@@ -77,4 +77,7 @@ dotnet_diagnostic.CA1032.severity = none
|
||||
dotnet_diagnostic.CA1826.severity = none
|
||||
|
||||
# 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">
|
||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.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>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -6,6 +6,25 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
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
|
||||
{
|
||||
[Fact]
|
||||
@@ -54,6 +73,30 @@ 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]
|
||||
public void Should_Render_Grid_Correctly()
|
||||
{
|
||||
|
||||
@@ -21,6 +21,22 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.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
|
||||
@@ -88,6 +104,34 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddEmptyRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddEmptyRow();
|
||||
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("│ │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
|
||||
@@ -39,14 +39,18 @@ namespace Spectre.Console.Tests.Unit
|
||||
|
||||
[Theory]
|
||||
[InlineData("bold", Decoration.Bold)]
|
||||
[InlineData("b", Decoration.Bold)]
|
||||
[InlineData("dim", Decoration.Dim)]
|
||||
[InlineData("i", Decoration.Italic)]
|
||||
[InlineData("italic", Decoration.Italic)]
|
||||
[InlineData("underline", Decoration.Underline)]
|
||||
[InlineData("u", Decoration.Underline)]
|
||||
[InlineData("invert", Decoration.Invert)]
|
||||
[InlineData("conceal", Decoration.Conceal)]
|
||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
||||
[InlineData("s", Decoration.Strikethrough)]
|
||||
public void Should_Parse_Decoration(string text, Decoration decoration)
|
||||
{
|
||||
// Given, When
|
||||
@@ -126,108 +130,83 @@ namespace Spectre.Console.Tests.Unit
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
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
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Keyword_Should_Return_Default_Style()
|
||||
public void Should_Return_True_If_Parsing_Succeeded()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("default", out var style);
|
||||
var result = Style.TryParse("bold", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Foreground.ShouldBe(Color.Default);
|
||||
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);
|
||||
style.Decoration.ShouldBe(Decoration.Bold);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Text_And_Decoration()
|
||||
public void Should_Return_False_If_Parsing_Failed()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("bold underline blue on green", out var style);
|
||||
|
||||
// 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 _);
|
||||
var result = Style.TryParse("lol", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
|
||||
@@ -21,6 +21,6 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets or sets the out buffer.
|
||||
/// </summary>
|
||||
public TextWriter Out { get; set; }
|
||||
public TextWriter? Out { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Spectre.Console
|
||||
ColorSystem.Standard => "4 bits",
|
||||
ColorSystem.EightBit => "8 bits",
|
||||
ColorSystem.TrueColor => "24 bits",
|
||||
_ => "?"
|
||||
_ => "?",
|
||||
};
|
||||
|
||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color color && Equals(color);
|
||||
}
|
||||
|
||||
@@ -57,15 +57,20 @@ namespace Spectre.Console.Composition
|
||||
private Dictionary<BorderPart, string> Initialize()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@@ -56,6 +58,11 @@ namespace Spectre.Console
|
||||
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;
|
||||
|
||||
@@ -97,6 +104,16 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Adds a new row to the grid.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,22 +9,22 @@ namespace Spectre.Console
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; } = false;
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding of the column.
|
||||
/// </summary>
|
||||
public Padding? Padding { get; set; } = null;
|
||||
public Padding? Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the column.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; } = null;
|
||||
public Justify? Alignment { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Spectre.Console
|
||||
Right = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Centered
|
||||
/// Centered.
|
||||
/// </summary>
|
||||
Center = 2,
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Spectre.Console.Composition
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Measurement measurement && Equals(measurement);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Padding padding && Equals(padding);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public sealed class Panel : IRenderable
|
||||
{
|
||||
private const int EdgeWidth = 2;
|
||||
|
||||
private readonly IRenderable _child;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,14 +28,14 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the panel contents.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; } = null;
|
||||
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; } = false;
|
||||
public bool Expand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding.
|
||||
@@ -61,13 +63,12 @@ namespace Spectre.Console
|
||||
{
|
||||
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||
|
||||
var edgeWidth = 2;
|
||||
var paddingWidth = Padding.GetHorizontalPadding();
|
||||
var childWidth = width - edgeWidth - paddingWidth;
|
||||
var childWidth = width - EdgeWidth - paddingWidth;
|
||||
|
||||
if (!Expand)
|
||||
{
|
||||
var measurement = _child.Measure(context, width - edgeWidth - paddingWidth);
|
||||
var measurement = _child.Measure(context, width - EdgeWidth - paddingWidth);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,12 @@ namespace Spectre.Console.Composition
|
||||
|
||||
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;
|
||||
IsLineBreak = lineBreak;
|
||||
}
|
||||
@@ -89,7 +94,7 @@ namespace Spectre.Console.Composition
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset where to split the segment.</param>
|
||||
/// <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)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public sealed partial class Table
|
||||
{
|
||||
private const int EdgeCount = 2;
|
||||
|
||||
// Calculate the widths of each column, including padding, not including borders.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
@@ -115,10 +117,9 @@ namespace Spectre.Console
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var edges = 2;
|
||||
var separators = _columns.Count - 1;
|
||||
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||
return separators + edges + padding;
|
||||
return separators + EdgeCount + padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ namespace Spectre.Console
|
||||
/// fit the available space. If <c>false</c>, the table width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = false;
|
||||
public bool Expand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the table.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to use
|
||||
@@ -54,7 +54,7 @@ namespace Spectre.Console
|
||||
public bool SafeBorder { get; set; } = true;
|
||||
|
||||
// Whether this is a grid or not.
|
||||
internal bool IsGrid { get; set; } = false;
|
||||
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
|
||||
@@ -81,7 +81,7 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(new TableColumn(column));
|
||||
AddColumn(new TableColumn(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,6 +95,11 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
if (_rows.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot add new columns to table with existing rows.");
|
||||
}
|
||||
|
||||
_columns.Add(column);
|
||||
}
|
||||
|
||||
@@ -109,7 +114,10 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
||||
foreach (var column in columns)
|
||||
{
|
||||
AddColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,7 +131,20 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => column));
|
||||
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>
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Spectre.Console
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments)
|
||||
private static IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments)
|
||||
{
|
||||
// Creates individual segments of line breaks.
|
||||
var result = new List<Segment>();
|
||||
@@ -228,7 +228,11 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Spectre.Console.Internal
|
||||
return ColorPalette.EightBit[number];
|
||||
}
|
||||
|
||||
public static string GetName(int number)
|
||||
public static string? GetName(int number)
|
||||
{
|
||||
_nameLookup.TryGetValue(number, out var name);
|
||||
return name;
|
||||
|
||||
@@ -15,14 +15,18 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
{ "none", Decoration.None },
|
||||
{ "bold", Decoration.Bold },
|
||||
{ "b", Decoration.Bold },
|
||||
{ "dim", Decoration.Dim },
|
||||
{ "italic", Decoration.Italic },
|
||||
{ "i", Decoration.Italic },
|
||||
{ "underline", Decoration.Underline },
|
||||
{ "u", Decoration.Underline },
|
||||
{ "invert", Decoration.Invert },
|
||||
{ "conceal", Decoration.Conceal },
|
||||
{ "slowblink", Decoration.SlowBlink },
|
||||
{ "rapidblink", Decoration.RapidBlink },
|
||||
{ "strikethrough", Decoration.Strikethrough },
|
||||
{ "s", Decoration.Strikethrough },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,9 @@ namespace Spectre.Console.Internal
|
||||
|
||||
public static string NormalizeLineEndings(this string text, bool native = false)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalized = text?.Replace("\r\n", "\n")
|
||||
?.Replace("\r", string.Empty);
|
||||
text ??= string.Empty;
|
||||
|
||||
var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty;
|
||||
if (native && !_alreadyNormalized)
|
||||
{
|
||||
normalized = normalized.Replace("\n", Environment.NewLine);
|
||||
@@ -35,7 +30,8 @@ namespace Spectre.Console.Internal
|
||||
|
||||
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
|
||||
{
|
||||
public static Text Parse(string text, Style style = null)
|
||||
public static Text Parse(string text, Style? style = null)
|
||||
{
|
||||
style ??= Style.Plain;
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Spectre.Console.Internal
|
||||
while (tokenizer.MoveNext())
|
||||
{
|
||||
var token = tokenizer.Current;
|
||||
if (token == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.Kind == MarkupTokenKind.Open)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
private readonly StringBuffer _reader;
|
||||
|
||||
public MarkupToken Current { get; private set; }
|
||||
public MarkupToken? Current { get; private set; }
|
||||
|
||||
public MarkupTokenizer(string text)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class StringBuffer : IDisposable
|
||||
{
|
||||
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")]
|
||||
private readonly StringReader _reader;
|
||||
private readonly int _length;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -12,16 +14,23 @@ namespace Spectre.Console.Internal
|
||||
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;
|
||||
}
|
||||
|
||||
public static bool TryParse(string text, out Style style)
|
||||
public static bool TryParse(string text, out Style? style)
|
||||
{
|
||||
style = Parse(text, out var error);
|
||||
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 effectiveForeground = (Color?)null;
|
||||
@@ -57,16 +66,30 @@ namespace Spectre.Console.Internal
|
||||
var color = ColorTable.GetColor(part);
|
||||
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
|
||||
{
|
||||
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)
|
||||
@@ -95,5 +118,82 @@ namespace Spectre.Console.Internal
|
||||
error = null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Spectre.Console.Internal
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -9,27 +10,21 @@
|
||||
<None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||
</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>
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</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>
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Spectre.Console
|
||||
/// if the conversion succeeded, or <c>null</c> if the conversion failed.
|
||||
/// </param>
|
||||
/// <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);
|
||||
}
|
||||
@@ -113,13 +113,13 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as Style);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Style other)
|
||||
public bool Equals(Style? other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user