Rename Style and Appearance

* Renames Style -> Decoration
* Renames Appearance -> Style
* Adds Style.Parse and Style.TryParse
This commit is contained in:
Patrik Svensson 2020-08-03 22:33:08 +02:00 committed by Patrik Svensson
parent c3286a4842
commit 98cf63f485
32 changed files with 691 additions and 405 deletions

View File

@ -50,11 +50,11 @@ like you usually do with the `System.Console` API, but prettier.
```csharp
AnsiConsole.Foreground = Color.CornflowerBlue;
AnsiConsole.Style = Styles.Underline | Styles.Bold;
AnsiConsole.Decoration = Decoration.Underline | Decoration.Bold;
AnsiConsole.WriteLine("Hello World!");
AnsiConsole.Reset();
AnsiConsole.MarkupLine("[yellow]{0}[/] [underline]world[/]!", "Goodbye");
AnsiConsole.MarkupLine("[bold yellow on red]{0}[/] [underline]world[/]!", "Goodbye");
```
If you want to get a reference to the default `IAnsiConsole`,
@ -64,7 +64,10 @@ you can access it via `AnsiConsole.Console`.
Sometimes it's useful to explicitly create a console with specific
capabilities, such as during unit testing when you want control
over the environment your code runs in.
over the environment your code runs in.
It's recommended to not use `AnsiConsole` in code that run as
part of a unit test.
```csharp
IAnsiConsole console = AnsiConsole.Create(

View File

@ -9,7 +9,7 @@ namespace Sample
{
// Use the static API to write some things to the console.
AnsiConsole.Foreground = Color.Chartreuse2;
AnsiConsole.Style = Styles.Underline | Styles.Bold;
AnsiConsole.Decoration = Decoration.Underline | Decoration.Bold;
AnsiConsole.WriteLine("Hello World!");
AnsiConsole.Reset();
AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities);
@ -40,10 +40,10 @@ namespace Sample
// and downgrade them to the specified color system.
console.WriteLine();
console.Foreground = Color.Chartreuse2;
console.Style = Styles.Underline | Styles.Bold;
console.Decoration = Decoration.Underline | Decoration.Bold;
console.WriteLine("Hello World!");
console.ResetColors();
console.ResetStyle();
console.ResetDecoration();
console.MarkupLine("Capabilities: [yellow underline]{0}[/]", console.Capabilities);
console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", console.Width, console.Height);
console.MarkupLine("[white on red]Good[/] [red]bye[/]!");

View File

@ -13,7 +13,7 @@ namespace Spectre.Console.Tests
public Encoding Encoding => _console.Encoding;
public Styles Style { get => _console.Style; set => _console.Style = value; }
public Decoration Decoration { get => _console.Decoration; set => _console.Decoration = value; }
public Color Foreground { get => _console.Foreground; set => _console.Foreground = value; }
public Color Background { get => _console.Background; set => _console.Background = value; }

View File

@ -13,7 +13,7 @@ namespace Spectre.Console.Tests
public int Width { get; }
public int Height { get; }
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }

View File

@ -6,20 +6,20 @@ namespace Spectre.Console.Tests.Unit
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(Styles.Bold, "\u001b[1mHello World")]
[InlineData(Styles.Dim, "\u001b[2mHello World")]
[InlineData(Styles.Italic, "\u001b[3mHello World")]
[InlineData(Styles.Underline, "\u001b[4mHello World")]
[InlineData(Styles.Invert, "\u001b[7mHello World")]
[InlineData(Styles.Conceal, "\u001b[8mHello World")]
[InlineData(Styles.SlowBlink, "\u001b[5mHello World")]
[InlineData(Styles.RapidBlink, "\u001b[6mHello World")]
[InlineData(Styles.Strikethrough, "\u001b[9mHello World")]
public void Should_Write_Style_Correctly(Styles style, string expected)
[InlineData(Decoration.Bold, "\u001b[1mHello World")]
[InlineData(Decoration.Dim, "\u001b[2mHello World")]
[InlineData(Decoration.Italic, "\u001b[3mHello World")]
[InlineData(Decoration.Underline, "\u001b[4mHello World")]
[InlineData(Decoration.Invert, "\u001b[7mHello World")]
[InlineData(Decoration.Conceal, "\u001b[8mHello World")]
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World")]
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World")]
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World")]
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Style = style;
fixture.Console.Decoration = decoration;
// When
fixture.Console.Write("Hello World");
@ -29,13 +29,13 @@ namespace Spectre.Console.Tests.Unit
}
[Theory]
[InlineData(Styles.Bold | Styles.Underline, "\u001b[1;4mHello World")]
[InlineData(Styles.Bold | Styles.Underline | Styles.Conceal, "\u001b[1;4;8mHello World")]
public void Should_Write_Combined_Styles_Correctly(Styles style, string expected)
[InlineData(Decoration.Bold | Decoration.Underline, "\u001b[1;4mHello World")]
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World")]
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Style = style;
fixture.Console.Decoration = decoration;
// When
fixture.Console.Write("Hello World");

View File

@ -8,13 +8,13 @@ namespace Spectre.Console.Tests.Unit
public partial class AnsiConsoleTests
{
[Fact]
public void Should_Combine_Style_And_Colors()
public void Should_Combine_Decoration_And_Colors()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@ -30,7 +30,7 @@ namespace Spectre.Console.Tests.Unit
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.Default;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@ -46,7 +46,7 @@ namespace Spectre.Console.Tests.Unit
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.Default;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@ -56,13 +56,13 @@ namespace Spectre.Console.Tests.Unit
}
[Fact]
public void Should_Not_Include_Style_If_Set_To_None()
public void Should_Not_Include_Decoration_If_Set_To_None()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.None;
fixture.Console.Decoration = Decoration.None;
// When
fixture.Console.Write("Hello");

View File

@ -1,24 +0,0 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class AppearanceTests
{
[Fact]
public void Should_Combine_Two_Appearances_As_Expected()
{
// Given
var first = new Appearance(Color.White, Color.Yellow, Styles.Bold | Styles.Italic);
var other = new Appearance(Color.Green, Color.Silver, Styles.Underline);
// When
var result = first.Combine(other);
// Then
result.Foreground.ShouldBe(Color.Green);
result.Background.ShouldBe(Color.Silver);
result.Style.ShouldBe(Styles.Bold | Styles.Italic | Styles.Underline);
}
}
}

View File

@ -12,17 +12,17 @@ namespace Spectre.Console.Tests.Unit
public void Should_Split_Segment_Correctly()
{
// Given
var appearance = new Appearance(Color.Red, Color.Green, Styles.Bold);
var segment = new Segment("Foo Bar", appearance);
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
var segment = new Segment("Foo Bar", style);
// When
var (first, second) = segment.Split(3);
// Then
first.Text.ShouldBe("Foo");
first.Appearance.ShouldBe(appearance);
first.Style.ShouldBe(style);
second.Text.ShouldBe(" Bar");
second.Appearance.ShouldBe(appearance);
second.Style.ShouldBe(style);
}
}

View File

@ -45,7 +45,7 @@ namespace Spectre.Console.Tests.Unit
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
var text = Text.New("Hello World");
text.Stylize(start: 3, end: 8, new Appearance(style: Styles.Underline));
text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline));
// When
fixture.Console.Render(text);
@ -62,7 +62,7 @@ namespace Spectre.Console.Tests.Unit
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, width: 5);
var text = Text.New("Hello World");
text.Stylize(start: 3, end: 8, new Appearance(style: Styles.Underline));
text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline));
// When
fixture.Console.Render(text);

View File

@ -0,0 +1,237 @@
using System;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class StyleTests
{
[Fact]
public void Should_Combine_Two_Styles_As_Expected()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic);
var other = new Style(Color.Green, Color.Silver, Decoration.Underline);
// When
var result = first.Combine(other);
// Then
result.Foreground.ShouldBe(Color.Green);
result.Background.ShouldBe(Color.Silver);
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline);
}
public sealed class TheParseMethod
{
[Fact]
public void Default_Keyword_Should_Return_Default_Style()
{
// Given, When
var result = Style.Parse("default");
// Then
result.ShouldNotBeNull();
result.Foreground.ShouldBe(Color.Default);
result.Background.ShouldBe(Color.Default);
result.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.Parse(text);
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(decoration);
}
[Fact]
public void Should_Parse_Text_And_Decoration()
{
// Given, When
var result = Style.Parse("bold underline blue on green");
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
result.Foreground.ShouldBe(Color.Blue);
result.Background.ShouldBe(Color.Green);
}
[Fact]
public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
{
// Given, When
var result = Style.Parse("default on green");
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.None);
result.Foreground.ShouldBe(Color.Default);
result.Background.ShouldBe(Color.Green);
}
[Fact]
public void Should_Throw_If_Foreground_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("green yellow"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A foreground color has already been set.");
}
[Fact]
public void Should_Throw_If_Background_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("green on blue yellow"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A background color has already been set.");
}
[Fact]
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
{
// Given, When
var result = Record.Exception(() => Style.Parse("bold lol"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Could not find color or style 'lol'.");
}
[Fact]
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
{
// Given, When
var result = Record.Exception(() => Style.Parse("blue on lol"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("Could not find color 'lol'.");
}
}
public sealed class TheTryParseMethod
{
[Fact]
public void Default_Keyword_Should_Return_Default_Style()
{
// Given, When
var result = Style.TryParse("default", 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);
}
[Fact]
public void Should_Parse_Text_And_Decoration()
{
// 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 var style);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Throw_If_Background_Is_Set_Twice()
{
// Given, When
var result = Style.TryParse("green on blue yellow", out var style);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
{
// Given, When
var result = Style.TryParse("bold lol", out var style);
// 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 style);
// Then
result.ShouldBeFalse();
}
}
}
}

View File

@ -63,12 +63,12 @@ namespace Spectre.Console
}
/// <summary>
/// Gets or sets the style.
/// Gets or sets the text decoration.
/// </summary>
public static Styles Style
public static Decoration Decoration
{
get => Console.Style;
set => Console.Style = value;
get => Console.Decoration;
set => Console.Decoration = value;
}
/// <summary>
@ -83,7 +83,7 @@ namespace Spectre.Console
}
/// <summary>
/// Resets colors and styles to the default ones.
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
@ -91,15 +91,15 @@ namespace Spectre.Console
}
/// <summary>
/// Resets the current style back to the default one.
/// Resets the current applied text decorations.
/// </summary>
public static void ResetStyle()
public static void ResetDecoration()
{
Console.ResetStyle();
Console.ResetDecoration();
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{

View File

@ -1,108 +0,0 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents color and style.
/// </summary>
public sealed class Appearance : IEquatable<Appearance>
{
/// <summary>
/// Gets the foreground color.
/// </summary>
public Color Foreground { get; }
/// <summary>
/// Gets the background color.
/// </summary>
public Color Background { get; }
/// <summary>
/// Gets the style.
/// </summary>
public Styles Style { get; }
/// <summary>
/// Gets an <see cref="Appearance"/> with the
/// default color and without style.
/// </summary>
public static Appearance Plain { get; } = new Appearance();
private Appearance()
: this(null, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Appearance"/> class.
/// </summary>
/// <param name="foreground">The foreground color.</param>
/// <param name="background">The background color.</param>
/// <param name="style">The style.</param>
public Appearance(Color? foreground = null, Color? background = null, Styles? style = null)
{
Foreground = foreground ?? Color.Default;
Background = background ?? Color.Default;
Style = style ?? Styles.None;
}
/// <summary>
/// Combines this appearance with another one.
/// </summary>
/// <param name="other">The item to combine with this.</param>
/// <returns>A new appearance representing a combination of this and the other one.</returns>
public Appearance Combine(Appearance other)
{
if (other is null)
{
throw new ArgumentNullException(nameof(other));
}
var foreground = Foreground;
if (!other.Foreground.IsDefault)
{
foreground = other.Foreground;
}
var background = Background;
if (!other.Background.IsDefault)
{
background = other.Background;
}
return new Appearance(foreground, background, Style | other.Style);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ Foreground.GetHashCode();
hash = (hash * 16777619) ^ Background.GetHashCode();
hash = (hash * 16777619) ^ Style.GetHashCode();
return hash;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return Equals(obj as Appearance);
}
/// <inheritdoc/>
public bool Equals(Appearance other)
{
if (other == null)
{
return false;
}
return Foreground.Equals(other.Foreground) &&
Background.Equals(other.Background) &&
Style == other.Style;
}
}
}

View File

@ -25,16 +25,16 @@ namespace Spectre.Console.Composition
public bool IsLineBreak { get; }
/// <summary>
/// Gets the appearance of the segment.
/// Gets the segment style.
/// </summary>
public Appearance Appearance { get; }
public Style Style { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Segment"/> class.
/// </summary>
/// <param name="text">The segment text.</param>
public Segment(string text)
: this(text, Appearance.Plain)
: this(text, Style.Plain)
{
}
@ -42,16 +42,16 @@ namespace Spectre.Console.Composition
/// Initializes a new instance of the <see cref="Segment"/> class.
/// </summary>
/// <param name="text">The segment text.</param>
/// <param name="appearance">The segment appearance.</param>
public Segment(string text, Appearance appearance)
: this(text, appearance, false)
/// <param name="style">The segment style.</param>
public Segment(string text, Style style)
: this(text, style, false)
{
}
private Segment(string text, Appearance appearance, bool lineBreak)
private Segment(string text, Style style, bool lineBreak)
{
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
Appearance = appearance;
Style = style;
IsLineBreak = lineBreak;
}
@ -61,7 +61,7 @@ namespace Spectre.Console.Composition
/// <returns>A segment that represents an implicit line break.</returns>
public static Segment LineBreak()
{
return new Segment("\n", Appearance.Plain, true);
return new Segment("\n", Style.Plain, true);
}
/// <summary>
@ -81,7 +81,7 @@ namespace Spectre.Console.Composition
/// <returns>A new segment without any trailing line endings.</returns>
public Segment StripLineEndings()
{
return new Segment(Text.TrimEnd('\n'), Appearance);
return new Segment(Text.TrimEnd('\n'), Style);
}
/// <summary>
@ -102,8 +102,8 @@ namespace Spectre.Console.Composition
}
return (
new Segment(Text.Substring(0, offset), Appearance),
new Segment(Text.Substring(offset, Text.Length - offset), Appearance));
new Segment(Text.Substring(0, offset), Style),
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
/// <summary>
@ -178,7 +178,7 @@ namespace Spectre.Console.Composition
{
if (parts[0].Length > 0)
{
line.Add(new Segment(parts[0], segment.Appearance));
line.Add(new Segment(parts[0], segment.Style));
}
}

View File

@ -9,7 +9,7 @@ using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Represents text with color and style.
/// Represents text with color and decorations.
/// </summary>
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Text : IRenderable
@ -21,13 +21,13 @@ namespace Spectre.Console
{
public int Start { get; }
public int End { get; }
public Appearance Appearance { get; }
public Style Style { get; }
public Span(int start, int end, Appearance appearance)
public Span(int start, int end, Style style)
{
Start = start;
End = end;
Appearance = appearance ?? Appearance.Plain;
Style = style ?? Style.Plain;
}
}
@ -47,21 +47,21 @@ namespace Spectre.Console
/// <param name="text">The text.</param>
/// <param name="foreground">The foreground.</param>
/// <param name="background">The background.</param>
/// <param name="style">The style.</param>
/// <param name="decoration">The text decoration.</param>
/// <returns>A <see cref="Text"/> instance.</returns>
public static Text New(
string text, Color? foreground = null, Color? background = null, Styles? style = null)
string text, Color? foreground = null, Color? background = null, Decoration? decoration = null)
{
var result = MarkupParser.Parse(text, new Appearance(foreground, background, style));
var result = MarkupParser.Parse(text, new Style(foreground, background, decoration));
return result;
}
/// <summary>
/// Appends some text with a style.
/// Appends some text with the specified color and decorations.
/// </summary>
/// <param name="text">The text to append.</param>
/// <param name="appearance">The appearance of the text.</param>
public void Append(string text, Appearance appearance)
/// <param name="style">The text style.</param>
public void Append(string text, Style style)
{
if (text == null)
{
@ -73,7 +73,7 @@ namespace Spectre.Console
_text += text;
Stylize(start, end, appearance);
Stylize(start, end, style);
}
/// <summary>
@ -81,8 +81,8 @@ namespace Spectre.Console
/// </summary>
/// <param name="start">The start position.</param>
/// <param name="end">The end position.</param>
/// <param name="appearance">The color and style to apply.</param>
public void Stylize(int start, int end, Appearance appearance)
/// <param name="style">The style to apply.</param>
public void Stylize(int start, int end, Style style)
{
if (start >= end)
{
@ -92,7 +92,7 @@ namespace Spectre.Console
start = Math.Max(start, 0);
end = Math.Min(end, _text.Length);
_spans.Add(new Span(start, end, appearance));
_spans.Add(new Span(start, end, style));
}
/// <inheritdoc/>
@ -149,7 +149,7 @@ namespace Spectre.Console
}
result.Add(Segment.LineBreak());
queue.Enqueue(new Segment(second.Text.Substring(1), second.Appearance));
queue.Enqueue(new Segment(second.Text.Substring(1), second.Style));
}
}
@ -162,8 +162,8 @@ namespace Spectre.Console
// https://github.com/willmcgugan/rich/blob/eb2f0d5277c159d8693636ec60c79c5442fd2e43/rich/text.py#L492
// Create the style map.
var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Appearance);
styleMap[0] = Appearance.Plain;
var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Style);
styleMap[0] = Style.Plain;
// Create a span list.
var spans = new List<(int Offset, bool Leaving, int Style)>();
@ -173,7 +173,7 @@ namespace Spectre.Console
spans.Add((_text.Length, true, 0));
spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList();
// Keep track of applied appearances using a stack
// Keep track of applied styles using a stack
var styleStack = new Stack<int>();
// Now build the segments.
@ -195,7 +195,7 @@ namespace Spectre.Console
{
// Build the current style from the stack
var styleIndices = styleStack.OrderBy(index => index).ToArray();
var currentStyle = Appearance.Plain.Combine(styleIndices.Select(index => styleMap[index]));
var currentStyle = Style.Plain.Combine(styleIndices.Select(index => styleMap[index]));
// Create segment
var text = _text.Substring(offset, Math.Min(_text.Length - offset, nextOffset - offset));

View File

@ -28,9 +28,9 @@ namespace Spectre.Console
foreach (var segment in renderable.Render(console.Encoding, console.Width))
{
if (!segment.Appearance.Equals(Appearance.Plain))
if (!segment.Style.Equals(Style.Plain))
{
using (var appearance = console.PushAppearance(segment.Appearance))
using (var style = console.PushStyle(segment.Style))
{
console.Write(segment.Text);
}

View File

@ -8,7 +8,7 @@ namespace Spectre.Console
public static partial class ConsoleExtensions
{
/// <summary>
/// Resets both colors and style for the console.
/// Resets colors and text decorations.
/// </summary>
/// <param name="console">The console to reset.</param>
public static void Reset(this IAnsiConsole console)
@ -19,25 +19,25 @@ namespace Spectre.Console
}
console.ResetColors();
console.ResetStyle();
console.ResetDecoration();
}
/// <summary>
/// Resets the current style back to the default one.
/// Resets the current applied text decorations.
/// </summary>
/// <param name="console">The console to reset the style for.</param>
public static void ResetStyle(this IAnsiConsole console)
/// <param name="console">The console to reset the text decorations for.</param>
public static void ResetDecoration(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Style = Styles.None;
console.Decoration = Decoration.None;
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// Resets the current applied foreground and background colors.
/// </summary>
/// <param name="console">The console to reset colors for.</param>
public static void ResetColors(this IAnsiConsole console)

View File

@ -1,18 +1,20 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console
{
/// <summary>
/// Represents a style.
/// Represents text decoration.
/// </summary>
/// <remarks>
/// Support for different styles is up to the terminal.
/// Support for text decorations is up to the terminal.
/// </remarks>
[Flags]
public enum Styles
[SuppressMessage("Naming", "CA1714:Flags enums should have plural names")]
public enum Decoration
{
/// <summary>
/// No style.
/// No text decoration.
/// </summary>
None = 0,

View File

@ -28,9 +28,9 @@ namespace Spectre.Console
Encoding Encoding { get; }
/// <summary>
/// Gets or sets the current style.
/// Gets or sets the current text decoration.
/// </summary>
Styles Style { get; set; }
Decoration Decoration { get; set; }
/// <summary>
/// Gets or sets the current foreground.

View File

@ -7,11 +7,11 @@ namespace Spectre.Console.Internal
public static string GetAnsi(
ColorSystem system,
string text,
Styles style,
Decoration decoration,
Color foreground,
Color background)
{
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration);
// Got foreground?
if (foreground != Color.Default)

View File

@ -2,52 +2,52 @@ using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class AnsiStyleBuilder
internal static class AnsiDecorationBuilder
{
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Styles style)
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
{
if ((style & Styles.Bold) != 0)
if ((decoration & Decoration.Bold) != 0)
{
yield return 1;
}
if ((style & Styles.Dim) != 0)
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((style & Styles.Italic) != 0)
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((style & Styles.Underline) != 0)
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((style & Styles.SlowBlink) != 0)
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((style & Styles.RapidBlink) != 0)
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((style & Styles.Invert) != 0)
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((style & Styles.Conceal) != 0)
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((style & Styles.Strikethrough) != 0)
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}

View File

@ -11,7 +11,7 @@ namespace Spectre.Console.Internal
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }
@ -50,21 +50,7 @@ namespace Spectre.Console.Internal
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
Foreground = Color.Default;
Background = Color.Default;
Style = Styles.None;
}
public void Reset(bool colors, bool styles)
{
if (colors)
{
Foreground = Color.Default;
Background = Color.Default;
}
if (styles)
{
Style = Styles.None;
}
Decoration = Decoration.None;
}
public void Write(string text)
@ -77,7 +63,7 @@ namespace Spectre.Console.Internal
_out.Write(AnsiBuilder.GetAnsi(
_system,
text.NormalizeLineEndings(native: true),
Style,
Decoration,
Foreground,
Background));
}

View File

@ -25,7 +25,7 @@ namespace Spectre.Console.Internal
{
return new AnsiConsoleRenderer(buffer, colorSystem)
{
Style = Styles.None,
Decoration = Decoration.None,
};
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class DecorationTable
{
private static readonly Dictionary<string, Decoration?> _lookup;
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
static DecorationTable()
{
_lookup = new Dictionary<string, Decoration?>(StringComparer.OrdinalIgnoreCase)
{
{ "none", Decoration.None },
{ "bold", Decoration.Bold },
{ "dim", Decoration.Dim },
{ "italic", Decoration.Italic },
{ "underline", Decoration.Underline },
{ "invert", Decoration.Invert },
{ "conceal", Decoration.Conceal },
{ "slowblink", Decoration.SlowBlink },
{ "rapidblink", Decoration.RapidBlink },
{ "strikethrough", Decoration.Strikethrough },
};
}
public static Decoration? GetDecoration(string name)
{
_lookup.TryGetValue(name, out var result);
return result;
}
}
}

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class AppearanceExtensions
{
public static Appearance Combine(this Appearance appearance, IEnumerable<Appearance> source)
{
var current = appearance;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@ -5,18 +5,23 @@ namespace Spectre.Console.Internal
{
internal static class ConsoleExtensions
{
public static IDisposable PushAppearance(this IAnsiConsole console, Appearance appearance)
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = new Appearance(console.Foreground, console.Background, console.Style);
console.SetColor(appearance.Foreground, true);
console.SetColor(appearance.Background, false);
console.Style = appearance.Style;
return new AppearanceScope(console, current);
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var current = new Style(console.Foreground, console.Background, console.Decoration);
console.SetColor(style.Foreground, true);
console.SetColor(style.Background, false);
console.Decoration = style.Decoration;
return new StyleScope(console, current);
}
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
@ -31,16 +36,16 @@ namespace Spectre.Console.Internal
return new ColorScope(console, current, foreground);
}
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Style;
console.Style = style;
return new StyleScope(console, current);
var current = console.Decoration;
console.Decoration = decoration;
return new DecorationScope(console, current);
}
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
@ -61,30 +66,30 @@ namespace Spectre.Console.Internal
}
}
internal sealed class AppearanceScope : IDisposable
internal sealed class StyleScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Appearance _apperance;
private readonly Style _style;
public AppearanceScope(IAnsiConsole console, Appearance appearance)
public StyleScope(IAnsiConsole console, Style style)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_apperance = appearance;
_style = style ?? throw new ArgumentNullException(nameof(style));
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~AppearanceScope()
~StyleScope()
{
throw new InvalidOperationException("Appearance scope was not disposed.");
throw new InvalidOperationException("Style scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_apperance.Foreground, true);
_console.SetColor(_apperance.Background, false);
_console.Style = _apperance.Style;
_console.SetColor(_style.Foreground, true);
_console.SetColor(_style.Background, false);
_console.Decoration = _style.Decoration;
}
}
@ -115,28 +120,28 @@ namespace Spectre.Console.Internal
}
}
internal sealed class StyleScope : IDisposable
internal sealed class DecorationScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Styles _style;
private readonly Decoration _decoration;
public StyleScope(IAnsiConsole console, Styles color)
public DecorationScope(IAnsiConsole console, Decoration decoration)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = color;
_decoration = decoration;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
~DecorationScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
throw new InvalidOperationException("Decoration scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.Style = _style;
_console.Decoration = _decoration;
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class StyleExtensions
{
public static Style Combine(this Style style, IEnumerable<Style> source)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (source is null)
{
return style;
}
var current = style;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@ -44,7 +44,7 @@ namespace Spectre.Console.Internal
}
}
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground
{

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class StyleTable
{
private static readonly Dictionary<string, Styles?> _styles;
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
static StyleTable()
{
_styles = new Dictionary<string, Styles?>(StringComparer.OrdinalIgnoreCase)
{
{ "bold", Styles.Bold },
{ "dim", Styles.Dim },
{ "italic", Styles.Italic },
{ "underline", Styles.Underline },
{ "invert", Styles.Invert },
{ "conceal", Styles.Conceal },
{ "slowblink", Styles.SlowBlink },
{ "rapidblink", Styles.RapidBlink },
{ "strikethrough", Styles.Strikethrough },
};
}
public static Styles? GetStyle(string name)
{
_styles.TryGetValue(name, out var style);
return style;
}
}
}

View File

@ -5,14 +5,14 @@ namespace Spectre.Console.Internal
{
internal static class MarkupParser
{
public static Text Parse(string text, Appearance appearance = null)
public static Text Parse(string text, Style style = null)
{
appearance ??= Appearance.Plain;
style ??= Style.Plain;
var result = new Text(string.Empty);
using var tokenizer = new MarkupTokenizer(text);
var stack = new Stack<Appearance>();
var stack = new Stack<Style>();
while (tokenizer.MoveNext())
{
@ -20,8 +20,8 @@ namespace Spectre.Console.Internal
if (token.Kind == MarkupTokenKind.Open)
{
var (style, foreground, background) = MarkupStyleParser.Parse(token.Value);
stack.Push(new Appearance(foreground, background, style));
var parsedStyle = StyleParser.Parse(token.Value);
stack.Push(parsedStyle);
}
else if (token.Kind == MarkupTokenKind.Close)
{
@ -35,8 +35,8 @@ namespace Spectre.Console.Internal
else if (token.Kind == MarkupTokenKind.Text)
{
// Get the effecive style.
var style = appearance.Combine(stack);
result.Append(token.Value, style);
var effectiveStyle = style.Combine(stack);
result.Append(token.Value, effectiveStyle);
}
else
{

View File

@ -1,65 +0,0 @@
using System;
namespace Spectre.Console.Internal
{
internal static class MarkupStyleParser
{
public static (Styles? Style, Color? Foreground, Color? Background) Parse(string text)
{
var effectiveStyle = (Styles?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null;
var parts = text.Split(new[] { ' ' });
var foreground = true;
foreach (var part in parts)
{
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
{
foreground = false;
continue;
}
var style = StyleTable.GetStyle(part);
if (style != null)
{
if (effectiveStyle == null)
{
effectiveStyle = Styles.None;
}
effectiveStyle |= style.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
throw new InvalidOperationException("Could not find color..");
}
if (foreground)
{
if (effectiveForeground != null)
{
throw new InvalidOperationException("A foreground has already been set.");
}
effectiveForeground = color;
}
else
{
if (effectiveBackground != null)
{
throw new InvalidOperationException("A background has already been set.");
}
effectiveBackground = color;
}
}
}
return (effectiveStyle, effectiveForeground, effectiveBackground);
}
}
}

View File

@ -0,0 +1,104 @@
using System;
namespace Spectre.Console.Internal
{
internal static class StyleParser
{
public static Style Parse(string text)
{
var style = Parse(text, out var error);
if (error != null)
{
throw new InvalidOperationException(error);
}
return style;
}
public static bool TryParse(string text, out Style style)
{
style = Parse(text, out var error);
if (error != null)
{
return false;
}
return true;
}
private static Style Parse(string text, out string error)
{
var effectiveDecoration = (Decoration?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null;
var parts = text.Split(new[] { ' ' });
var foreground = true;
foreach (var part in parts)
{
if (part.Equals("default", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
{
foreground = false;
continue;
}
var decoration = DecorationTable.GetDecoration(part);
if (decoration != null)
{
if (effectiveDecoration == null)
{
effectiveDecoration = Decoration.None;
}
effectiveDecoration |= decoration.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
if (!foreground)
{
error = $"Could not find color '{part}'.";
}
else
{
error = $"Could not find color or style '{part}'.";
}
return null;
}
if (foreground)
{
if (effectiveForeground != null)
{
error = "A foreground color has already been set.";
return null;
}
effectiveForeground = color;
}
else
{
if (effectiveBackground != null)
{
error = "A background color has already been set.";
return null;
}
effectiveBackground = color;
}
}
}
error = null;
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration);
}
}
}

View File

@ -0,0 +1,134 @@
using System;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed class Style : IEquatable<Style>
{
/// <summary>
/// Gets the foreground color.
/// </summary>
public Color Foreground { get; }
/// <summary>
/// Gets the background color.
/// </summary>
public Color Background { get; }
/// <summary>
/// Gets the text decoration.
/// </summary>
public Decoration Decoration { get; }
/// <summary>
/// Gets an <see cref="Style"/> with the
/// default colors and without text decoration.
/// </summary>
public static Style Plain { get; } = new Style();
private Style()
: this(null, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Style"/> class.
/// </summary>
/// <param name="foreground">The foreground color.</param>
/// <param name="background">The background color.</param>
/// <param name="decoration">The text decoration.</param>
public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null)
{
Foreground = foreground ?? Color.Default;
Background = background ?? Color.Default;
Decoration = decoration ?? Decoration.None;
}
/// <summary>
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
/// </summary>
/// <param name="text">A string containing a style to parse.</param>
/// <returns>A <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>.</returns>
public static Style Parse(string text)
{
return StyleParser.Parse(text);
}
/// <summary>
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="text">A string containing a style to parse.</param>
/// <param name="result">
/// When this method returns, contains the <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>,
/// 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)
{
return StyleParser.TryParse(text, out result);
}
/// <summary>
/// Combines this style with another one.
/// </summary>
/// <param name="other">The item to combine with this.</param>
/// <returns>A new style representing a combination of this and the other one.</returns>
public Style Combine(Style other)
{
if (other is null)
{
throw new ArgumentNullException(nameof(other));
}
var foreground = Foreground;
if (!other.Foreground.IsDefault)
{
foreground = other.Foreground;
}
var background = Background;
if (!other.Background.IsDefault)
{
background = other.Background;
}
return new Style(foreground, background, Decoration | other.Decoration);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ Foreground.GetHashCode();
hash = (hash * 16777619) ^ Background.GetHashCode();
hash = (hash * 16777619) ^ Decoration.GetHashCode();
return hash;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return Equals(obj as Style);
}
/// <inheritdoc/>
public bool Equals(Style other)
{
if (other == null)
{
return false;
}
return Foreground.Equals(other.Foreground) &&
Background.Equals(other.Background) &&
Decoration == other.Decoration;
}
}
}