From 504746c5dc1d1c39b3a1e10bd6cea49dbc7f8bca Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Fri, 11 Sep 2020 00:34:45 +0200 Subject: [PATCH] Add link support for supported terminals Also refactors the code quite a bit, to make it a bit more easier to add features like this in the future. Closes #75 --- examples/Links/Links.csproj | 14 + examples/Links/Program.cs | 22 + .../Extensions/ConsoleExtensions.cs | 24 -- .../Extensions/StyleExtensions.cs | 15 + .../Fixtures/ConsoleWithWidth.cs | 10 +- .../Fixtures/PlainConsole.cs | 3 +- .../Unit/AnsiConsoleTests.Colors.cs | 33 +- .../Unit/AnsiConsoleTests.Markup.cs | 2 + .../Unit/AnsiConsoleTests.Style.cs | 6 +- .../Unit/AnsiConsoleTests.cs | 389 ++---------------- src/Spectre.Console.Tests/Unit/StyleTests.cs | 117 +++++- src/Spectre.Console.sln | 17 +- src/Spectre.Console/AnsiConsole.State.cs | 85 ++++ src/Spectre.Console/AnsiConsole.Write.cs | 54 +-- src/Spectre.Console/AnsiConsole.WriteLine.cs | 56 +-- src/Spectre.Console/AnsiConsole.cs | 56 +-- src/Spectre.Console/Capabilities.cs | 11 + .../AnsiConsoleExtensions.Rendering.cs | 29 +- .../Extensions/AnsiConsoleExtensions.Write.cs | 339 --------------- .../AnsiConsoleExtensions.WriteLine.cs | 368 ----------------- .../Extensions/AnsiConsoleExtensions.cs | 35 +- .../Extensions/StyleExtensions.cs | 21 + src/Spectre.Console/IAnsiConsole.cs | 18 +- .../Internal/Ansi/AnsiBuilder.cs | 44 +- .../Internal/AnsiConsoleRenderer.cs | 14 +- .../Internal/ConsoleBuilder.cs | 5 +- src/Spectre.Console/Internal/Constants.cs | 2 + .../Extensions/AnsiConsoleExtensions.cs | 147 ------- .../Internal/Extensions/StringExtensions.cs | 23 ++ .../Internal/FallbackConsoleRenderer.cs | 84 ++-- .../Internal/Text/StyleParser.cs | 24 +- src/Spectre.Console/Spectre.Console.csproj | 6 - src/Spectre.Console/Style.cs | 40 +- 33 files changed, 574 insertions(+), 1539 deletions(-) create mode 100644 examples/Links/Links.csproj create mode 100644 examples/Links/Program.cs delete mode 100644 src/Spectre.Console.Tests/Extensions/ConsoleExtensions.cs create mode 100644 src/Spectre.Console.Tests/Extensions/StyleExtensions.cs create mode 100644 src/Spectre.Console/AnsiConsole.State.cs delete mode 100644 src/Spectre.Console/Extensions/AnsiConsoleExtensions.Write.cs delete mode 100644 src/Spectre.Console/Extensions/AnsiConsoleExtensions.WriteLine.cs delete mode 100644 src/Spectre.Console/Internal/Extensions/AnsiConsoleExtensions.cs diff --git a/examples/Links/Links.csproj b/examples/Links/Links.csproj new file mode 100644 index 0000000..5b35d6f --- /dev/null +++ b/examples/Links/Links.csproj @@ -0,0 +1,14 @@ + + + + Exe + netcoreapp3.1 + false + Demonstrates how to render links in a console. + + + + + + + diff --git a/examples/Links/Program.cs b/examples/Links/Program.cs new file mode 100644 index 0000000..6ceaa6a --- /dev/null +++ b/examples/Links/Program.cs @@ -0,0 +1,22 @@ +using System; +using Spectre.Console; + +namespace Links +{ + public static class Program + { + public static void Main() + { + if (AnsiConsole.Capabilities.SupportLinks) + { + AnsiConsole.MarkupLine("[link=https://patriksvensson.se]Click to visit my blog[/]!"); + } + else + { + AnsiConsole.MarkupLine("[red]It looks like your terminal doesn't support links[/]"); + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[yellow](╯°□°)╯[/]︵ [blue]┻━┻[/]"); + } + } + } +} diff --git a/src/Spectre.Console.Tests/Extensions/ConsoleExtensions.cs b/src/Spectre.Console.Tests/Extensions/ConsoleExtensions.cs deleted file mode 100644 index 01282ee..0000000 --- a/src/Spectre.Console.Tests/Extensions/ConsoleExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Spectre.Console.Tests -{ - public static class ConsoleExtensions - { - public static void SetColor(this IAnsiConsole console, Color color, bool foreground) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (foreground) - { - console.Foreground = color; - } - else - { - console.Background = color; - } - } - } -} diff --git a/src/Spectre.Console.Tests/Extensions/StyleExtensions.cs b/src/Spectre.Console.Tests/Extensions/StyleExtensions.cs new file mode 100644 index 0000000..92af879 --- /dev/null +++ b/src/Spectre.Console.Tests/Extensions/StyleExtensions.cs @@ -0,0 +1,15 @@ +namespace Spectre.Console.Tests +{ + internal static class StyleExtensions + { + public static Style SetColor(this Style style, Color color, bool foreground) + { + if (foreground) + { + return style.WithForeground(color); + } + + return style.WithBackground(color); + } + } +} diff --git a/src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs b/src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs index dde146c..4ba1bda 100644 --- a/src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs +++ b/src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Spectre.Console.Tests { @@ -13,19 +13,15 @@ namespace Spectre.Console.Tests public Encoding Encoding => _console.Encoding; - 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; } - public ConsoleWithWidth(IAnsiConsole console, int width) { _console = console; Width = width; } - public void Write(string text) + public void Write(string text, Style style) { - _console.Write(text); + _console.Write(text, style); } } } diff --git a/src/Spectre.Console.Tests/Fixtures/PlainConsole.cs b/src/Spectre.Console.Tests/Fixtures/PlainConsole.cs index 887d983..af2f169 100644 --- a/src/Spectre.Console.Tests/Fixtures/PlainConsole.cs +++ b/src/Spectre.Console.Tests/Fixtures/PlainConsole.cs @@ -16,6 +16,7 @@ namespace Spectre.Console.Tests public Decoration Decoration { get; set; } public Color Foreground { get; set; } public Color Background { get; set; } + public string Link { get; set; } public StringWriter Writer { get; } public string RawOutput => Writer.ToString(); @@ -39,7 +40,7 @@ namespace Spectre.Console.Tests Writer.Dispose(); } - public void Write(string text) + public void Write(string text, Style style) { Writer.Write(text); } diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Colors.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Colors.cs index d476ee0..161e58f 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Colors.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Colors.cs @@ -14,10 +14,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); - fixture.Console.SetColor(new Color(128, 0, 128), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(128, 0, 128), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -30,10 +29,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); - fixture.Console.SetColor(Color.Purple, foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(Color.Purple, foreground)); // Then fixture.Output.ShouldBe(expected); @@ -49,10 +47,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); - fixture.Console.SetColor(Color.Olive, foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(Color.Olive, foreground)); // Then fixture.Output.ShouldBe(expected); @@ -65,10 +62,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); - fixture.Console.SetColor(new Color(128, 128, 0), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(128, 128, 0), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -81,10 +77,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); - fixture.Console.SetColor(new Color(126, 127, 0), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(126, 127, 0), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -100,10 +95,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.SetColor(Color.Olive, foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(Color.Olive, foreground)); // Then fixture.Output.ShouldBe(expected); @@ -121,10 +115,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.SetColor(new Color(r, g, b), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -142,10 +135,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.SetColor(new Color(r, g, b), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -161,10 +153,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); - fixture.Console.SetColor(Color.Olive, foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(Color.Olive, foreground)); // Then fixture.Output.ShouldBe(expected); @@ -182,10 +173,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); - fixture.Console.SetColor(new Color(r, g, b), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground)); // Then fixture.Output.ShouldBe(expected); @@ -203,10 +193,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); - fixture.Console.SetColor(new Color(r, g, b), foreground); // When - fixture.Console.Write("Hello"); + fixture.Console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground)); // Then fixture.Output.ShouldBe(expected); diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs index 1303104..5f0136e 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Markup.cs @@ -13,6 +13,8 @@ namespace Spectre.Console.Tests.Unit [Theory] [InlineData("[yellow]Hello[/]", "Hello")] [InlineData("[yellow]Hello [italic]World[/]![/]", "Hello World!")] + [InlineData("[link=https://patriksvensson.se]Click to visit my blog[/]", "]8;id=2026695893;https://patriksvensson.se\\Click to visit my blog]8;;\\")] + [InlineData("[link]https://patriksvensson.se[/]", "]8;id=2026695893;https://patriksvensson.se\\https://patriksvensson.se]8;;\\")] public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected) { // Given diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs index ced16e6..5c3fb53 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs @@ -19,10 +19,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); - fixture.Console.Decoration = decoration; // When - fixture.Console.Write("Hello World"); + fixture.Console.Write("Hello World", Style.WithDecoration(decoration)); // Then fixture.Output.ShouldBe(expected); @@ -35,10 +34,9 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); - fixture.Console.Decoration = decoration; // When - fixture.Console.Write("Hello World"); + fixture.Console.Write("Hello World", Style.WithDecoration(decoration)); // Then fixture.Output.ShouldBe(expected); diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs index d8f0fee..ebfe152 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using Shouldly; using Xunit; @@ -12,12 +11,13 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.Foreground = Color.RoyalBlue1; - fixture.Console.Background = Color.NavajoWhite1; - fixture.Console.Decoration = Decoration.Italic; // When - fixture.Console.Write("Hello"); + fixture.Console.Write( + "Hello", + Style.WithForeground(Color.RoyalBlue1) + .WithBackground(Color.NavajoWhite1) + .WithDecoration(Decoration.Italic)); // Then fixture.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); @@ -28,12 +28,13 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.Foreground = Color.Default; - fixture.Console.Background = Color.NavajoWhite1; - fixture.Console.Decoration = Decoration.Italic; // When - fixture.Console.Write("Hello"); + fixture.Console.Write( + "Hello", + Style.WithForeground(Color.Default) + .WithBackground(Color.NavajoWhite1) + .WithDecoration(Decoration.Italic)); // Then fixture.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); @@ -44,12 +45,13 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.Foreground = Color.RoyalBlue1; - fixture.Console.Background = Color.Default; - fixture.Console.Decoration = Decoration.Italic; // When - fixture.Console.Write("Hello"); + fixture.Console.Write( + "Hello", + Style.WithForeground(Color.RoyalBlue1) + .WithBackground(Color.Default) + .WithDecoration(Decoration.Italic)); // Then fixture.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); @@ -60,190 +62,18 @@ namespace Spectre.Console.Tests.Unit { // Given var fixture = new AnsiConsoleFixture(ColorSystem.Standard); - fixture.Console.Foreground = Color.RoyalBlue1; - fixture.Console.Background = Color.NavajoWhite1; - fixture.Console.Decoration = Decoration.None; // When - fixture.Console.Write("Hello"); + fixture.Console.Write( + "Hello", + Style.WithForeground(Color.RoyalBlue1) + .WithBackground(Color.NavajoWhite1) + .WithDecoration(Decoration.None)); // Then fixture.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); } - public sealed class Write - { - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Int32_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32); - - // Then - fixture.Output.ShouldBe("32"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_UInt32_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32U); - - // Then - fixture.Output.ShouldBe("32"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Int64_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32L); - - // Then - fixture.Output.ShouldBe("32"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_UInt64_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32UL); - - // Then - fixture.Output.ShouldBe("32"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Single_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32.432F); - - // Then - fixture.Output.ShouldBe("32.432"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Double_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, (double)32.432); - - // Then - fixture.Output.ShouldBe("32.432"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Decimal_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 32.432M); - - // Then - fixture.Output.ShouldBe("32.432"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Boolean_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, true); - - // Then - fixture.Output.ShouldBe("True"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Char_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write(CultureInfo.InvariantCulture, 'P'); - - // Then - fixture.Output.ShouldBe("P"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Char_Array_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write( - CultureInfo.InvariantCulture, - new[] { 'P', 'a', 't', 'r', 'i', 'k' }); - - // Then - fixture.Output.ShouldBe("Patrik"); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Formatted_String_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.Write( - CultureInfo.InvariantCulture, - "Hello {0}! {1}", - "World", 32); - - // Then - fixture.Output.ShouldBe("Hello World! 32"); - } - } - public sealed class WriteLine { [Fact] @@ -253,10 +83,8 @@ namespace Spectre.Console.Tests.Unit var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); // When - fixture.Console.Background = ConsoleColor.Red; - fixture.Console.WriteLine("Hello"); - fixture.Console.Background = ConsoleColor.Green; - fixture.Console.WriteLine("World"); + fixture.Console.WriteLine("Hello", Style.WithBackground(ConsoleColor.Red)); + fixture.Console.WriteLine("World", Style.WithBackground(ConsoleColor.Green)); // Then fixture.Output.NormalizeLineEndings() @@ -270,183 +98,12 @@ namespace Spectre.Console.Tests.Unit var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); // When - fixture.Console.Background = ConsoleColor.Red; - fixture.Console.WriteLine("Hello\nWorld"); + fixture.Console.WriteLine("Hello\nWorld", Style.WithBackground(ConsoleColor.Red)); // Then fixture.Output.NormalizeLineEndings() .ShouldBe("Hello\nWorld\n"); } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Int32_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32); - - // Then - fixture.Output.ShouldBe("32" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_UInt32_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32U); - - // Then - fixture.Output.ShouldBe("32" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Int64_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32L); - - // Then - fixture.Output.ShouldBe("32" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_UInt64_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32UL); - - // Then - fixture.Output.ShouldBe("32" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Single_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32.432F); - - // Then - fixture.Output.ShouldBe("32.432" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Double_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, (double)32.432); - - // Then - fixture.Output.ShouldBe("32.432" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Decimal_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32.432M); - - // Then - fixture.Output.ShouldBe("32.432" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Boolean_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, true); - - // Then - fixture.Output.ShouldBe("True" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Char_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine(CultureInfo.InvariantCulture, 'P'); - - // Then - fixture.Output.ShouldBe("P" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Char_Array_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine( - CultureInfo.InvariantCulture, - new[] { 'P', 'a', 't', 'r', 'i', 'k' }); - - // Then - fixture.Output.ShouldBe("Patrik" + Environment.NewLine); - } - - [Theory] - [InlineData(AnsiSupport.Yes)] - [InlineData(AnsiSupport.No)] - public void Should_Write_Formatted_String_With_Format_Provider(AnsiSupport ansi) - { - // Given - var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi); - - // When - fixture.Console.WriteLine( - CultureInfo.InvariantCulture, - "Hello {0}! {1}", - "World", 32); - - // Then - fixture.Output.ShouldBe("Hello World! 32" + Environment.NewLine); - } } } } diff --git a/src/Spectre.Console.Tests/Unit/StyleTests.cs b/src/Spectre.Console.Tests/Unit/StyleTests.cs index dda6994..9b246ac 100644 --- a/src/Spectre.Console.Tests/Unit/StyleTests.cs +++ b/src/Spectre.Console.Tests/Unit/StyleTests.cs @@ -11,7 +11,7 @@ namespace Spectre.Console.Tests.Unit { // Given var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic); - var other = new Style(Color.Green, Color.Silver, Decoration.Underline); + var other = new Style(Color.Green, Color.Silver, Decoration.Underline, "https://example.com"); // When var result = first.Combine(other); @@ -20,6 +20,77 @@ namespace Spectre.Console.Tests.Unit result.Foreground.ShouldBe(Color.Green); result.Background.ShouldBe(Color.Silver); result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline); + result.Link.ShouldBe("https://example.com"); + } + + [Fact] + public void Should_Consider_Two_Identical_Styles_Equal() + { + // Given + var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + + // When + var result = first.Equals(second); + + // Then + result.ShouldBeTrue(); + } + + [Fact] + public void Should_Not_Consider_Two_Styles_With_Different_Foreground_Colors_Equal() + { + // Given + var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + var second = new Style(Color.Blue, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + + // When + var result = first.Equals(second); + + // Then + result.ShouldBeFalse(); + } + + [Fact] + public void Should_Not_Consider_Two_Styles_With_Different_Background_Colors_Equal() + { + // Given + var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + var second = new Style(Color.White, Color.Blue, Decoration.Bold | Decoration.Italic, "http://example.com"); + + // When + var result = first.Equals(second); + + // Then + result.ShouldBeFalse(); + } + + [Fact] + public void Should_Not_Consider_Two_Styles_With_Different_Decorations_Equal() + { + // Given + var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + var second = new Style(Color.White, Color.Yellow, Decoration.Bold, "http://example.com"); + + // When + var result = first.Equals(second); + + // Then + result.ShouldBeFalse(); + } + + [Fact] + public void Should_Not_Consider_Two_Styles_With_Different_Links_Equal() + { + // Given + var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com"); + var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://foo.com"); + + // When + var result = first.Equals(second); + + // Then + result.ShouldBeFalse(); } public sealed class TheParseMethod @@ -62,16 +133,36 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Parse_Text_And_Decoration() + public void Should_Parse_Link_Without_Address() { // Given, When - var result = Style.Parse("bold underline blue on green"); + var result = Style.Parse("link"); // Then result.ShouldNotBeNull(); - result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline); - result.Foreground.ShouldBe(Color.Blue); - result.Background.ShouldBe(Color.Green); + result.Link.ShouldBe("https://emptylink"); + } + + [Fact] + public void Should_Parse_Link() + { + // Given, When + var result = Style.Parse("link=https://example.com"); + + // Then + result.ShouldNotBeNull(); + result.Link.ShouldBe("https://example.com"); + } + + [Fact] + public void Should_Throw_If_Link_Is_Set_Twice() + { + // Given, When + var result = Record.Exception(() => Style.Parse("link=https://example.com link=https://example.com")); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldBe("A link has already been set."); } [Fact] @@ -131,6 +222,20 @@ namespace Spectre.Console.Tests.Unit result.Message.ShouldBe("Could not find color 'lol'."); } + [Fact] + public void Should_Parse_Colors_And_Decoration_And_Link() + { + // Given, When + var result = Style.Parse("link=https://example.com 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); + result.Link.ShouldBe("https://example.com"); + } + [Theory] [InlineData("#FF0000 on #0000FF")] [InlineData("#F00 on #00F")] diff --git a/src/Spectre.Console.sln b/src/Spectre.Console.sln index 1341d73..5b115ac 100644 --- a/src/Spectre.Console.sln +++ b/src/Spectre.Console.sln @@ -29,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Columns", "..\examples\Colu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Info", "..\examples\Info\Info.csproj", "{225CE0D4-06AB-411A-8D29-707504FE53B3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Borders", "..\examples\Borders\Borders.csproj", "{094245E6-4C94-485D-B5AC-3153E878B112}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Borders", "..\examples\Borders\Borders.csproj", "{094245E6-4C94-485D-B5AC-3153E878B112}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Links", "..\examples\Links\Links.csproj", "{6AF8C93B-AA41-4F44-8B1B-B8D166576174}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -149,6 +151,18 @@ Global {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x64.Build.0 = Release|Any CPU {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.ActiveCfg = Release|Any CPU {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.Build.0 = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x64.ActiveCfg = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x64.Build.0 = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x86.ActiveCfg = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x86.Build.0 = Debug|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|Any CPU.Build.0 = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x64.ActiveCfg = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x64.Build.0 = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.ActiveCfg = Release|Any CPU + {6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -161,6 +175,7 @@ Global {33357599-C79D-4299-888F-634E2C3EACEF} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {225CE0D4-06AB-411A-8D29-707504FE53B3} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {094245E6-4C94-485D-B5AC-3153E878B112} = {F0575243-121F-4DEE-9F6B-246E26DC0844} + {6AF8C93B-AA41-4F44-8B1B-B8D166576174} = {F0575243-121F-4DEE-9F6B-246E26DC0844} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} diff --git a/src/Spectre.Console/AnsiConsole.State.cs b/src/Spectre.Console/AnsiConsole.State.cs new file mode 100644 index 0000000..31e0503 --- /dev/null +++ b/src/Spectre.Console/AnsiConsole.State.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using Spectre.Console.Internal; + +namespace Spectre.Console +{ + /// + /// A console capable of writing ANSI escape sequences. + /// + public static partial class AnsiConsole + { + private static ConsoleColor _defaultForeground; + private static ConsoleColor _defaultBackground; + + internal static Style CurrentStyle { get; private set; } = Style.Plain; + internal static bool Created { get; private set; } + + /// + /// Gets or sets the foreground color. + /// + public static Color Foreground + { + get => CurrentStyle.Foreground; + set => CurrentStyle = CurrentStyle.WithForeground(value); + } + + /// + /// Gets or sets the background color. + /// + public static Color Background + { + get => CurrentStyle.Background; + set => CurrentStyle = CurrentStyle.WithBackground(value); + } + + /// + /// Gets or sets the text decoration. + /// + public static Decoration Decoration + { + get => CurrentStyle.Decoration; + set => CurrentStyle = CurrentStyle.WithDecoration(value); + } + + internal static void Initialize(TextWriter? @out) + { + if (@out?.IsStandardOut() ?? false) + { + Foreground = _defaultForeground = System.Console.ForegroundColor; + Background = _defaultBackground = System.Console.BackgroundColor; + } + else + { + Foreground = _defaultForeground = Color.Silver; + Background = _defaultBackground = Color.Black; + } + } + + /// + /// Resets colors and text decorations. + /// + public static void Reset() + { + ResetColors(); + ResetDecoration(); + } + + /// + /// Resets the current applied text decorations. + /// + public static void ResetDecoration() + { + Decoration = Decoration.None; + } + + /// + /// Resets the current applied foreground and background colors. + /// + public static void ResetColors() + { + Foreground = _defaultForeground; + Background = _defaultBackground; + } + } +} diff --git a/src/Spectre.Console/AnsiConsole.Write.cs b/src/Spectre.Console/AnsiConsole.Write.cs index aa9cdb6..f974cb5 100644 --- a/src/Spectre.Console/AnsiConsole.Write.cs +++ b/src/Spectre.Console/AnsiConsole.Write.cs @@ -14,7 +14,7 @@ namespace Spectre.Console /// The value to write. public static void Write(string value) { - Console.Write(value); + Console.Write(value, CurrentStyle); } /// @@ -24,7 +24,7 @@ namespace Spectre.Console /// The value to write. public static void Write(int value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -35,7 +35,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, int value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -45,7 +45,7 @@ namespace Spectre.Console /// The value to write. public static void Write(uint value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -56,7 +56,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, uint value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -66,7 +66,7 @@ namespace Spectre.Console /// The value to write. public static void Write(long value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -77,7 +77,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, long value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -87,7 +87,7 @@ namespace Spectre.Console /// The value to write. public static void Write(ulong value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -98,7 +98,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, ulong value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -108,7 +108,7 @@ namespace Spectre.Console /// The value to write. public static void Write(float value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -119,7 +119,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, float value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -129,7 +129,7 @@ namespace Spectre.Console /// The value to write. public static void Write(double value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -140,7 +140,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, double value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -149,7 +149,7 @@ namespace Spectre.Console /// The value to write. public static void Write(decimal value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -159,7 +159,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, decimal value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -168,7 +168,7 @@ namespace Spectre.Console /// The value to write. public static void Write(bool value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -178,7 +178,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, bool value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -187,7 +187,7 @@ namespace Spectre.Console /// The value to write. public static void Write(char value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -197,7 +197,7 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, char value) { - Console.Write(value.ToString(provider)); + Console.Write(value.ToString(provider), CurrentStyle); } /// @@ -206,7 +206,7 @@ namespace Spectre.Console /// The value to write. public static void Write(char[] value) { - Console.Write(CultureInfo.CurrentCulture, value); + Write(CultureInfo.CurrentCulture, value); } /// @@ -216,7 +216,15 @@ namespace Spectre.Console /// The value to write. public static void Write(IFormatProvider provider, char[] value) { - Console.Write(provider, value); + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + Console.Write(value[index].ToString(provider), CurrentStyle); + } } /// @@ -227,7 +235,7 @@ namespace Spectre.Console /// An array of objects to write. public static void Write(string format, params object[] args) { - Console.Write(CultureInfo.CurrentCulture, format, args); + Write(CultureInfo.CurrentCulture, format, args); } /// @@ -239,7 +247,7 @@ namespace Spectre.Console /// An array of objects to write. public static void Write(IFormatProvider provider, string format, params object[] args) { - Console.Write(string.Format(provider, format, args)); + Console.Write(string.Format(provider, format, args), CurrentStyle); } } } diff --git a/src/Spectre.Console/AnsiConsole.WriteLine.cs b/src/Spectre.Console/AnsiConsole.WriteLine.cs index a00f183..90075b5 100644 --- a/src/Spectre.Console/AnsiConsole.WriteLine.cs +++ b/src/Spectre.Console/AnsiConsole.WriteLine.cs @@ -22,7 +22,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(string value) { - Console.WriteLine(value); + Console.WriteLine(value, CurrentStyle); } /// @@ -32,7 +32,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(int value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -43,7 +43,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, int value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -53,7 +53,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(uint value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -64,7 +64,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, uint value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -74,7 +74,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(long value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -85,7 +85,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, long value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -95,7 +95,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(ulong value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -106,7 +106,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, ulong value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -116,7 +116,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(float value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -127,7 +127,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, float value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -137,7 +137,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(double value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -148,7 +148,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, double value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -158,7 +158,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(decimal value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -169,7 +169,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, decimal value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -179,7 +179,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(bool value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -190,7 +190,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, bool value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -200,7 +200,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(char value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -211,7 +211,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, char value) { - Console.WriteLine(value.ToString(provider)); + Console.WriteLine(value.ToString(provider), CurrentStyle); } /// @@ -221,7 +221,7 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(char[] value) { - Console.WriteLine(CultureInfo.CurrentCulture, value); + WriteLine(CultureInfo.CurrentCulture, value); } /// @@ -232,7 +232,17 @@ namespace Spectre.Console /// The value to write. public static void WriteLine(IFormatProvider provider, char[] value) { - Console.WriteLine(provider, value); + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + Console.Write(value[index].ToString(provider), CurrentStyle); + } + + Console.WriteLine(); } /// @@ -244,7 +254,7 @@ namespace Spectre.Console /// An array of objects to write. public static void WriteLine(string format, params object[] args) { - Console.WriteLine(CultureInfo.CurrentCulture, format, args); + WriteLine(CultureInfo.CurrentCulture, format, args); } /// @@ -257,7 +267,7 @@ namespace Spectre.Console /// An array of objects to write. public static void WriteLine(IFormatProvider provider, string format, params object[] args) { - Console.WriteLine(string.Format(provider, format, args)); + Console.WriteLine(string.Format(provider, format, args), CurrentStyle); } } } diff --git a/src/Spectre.Console/AnsiConsole.cs b/src/Spectre.Console/AnsiConsole.cs index e88b67e..b2c607b 100644 --- a/src/Spectre.Console/AnsiConsole.cs +++ b/src/Spectre.Console/AnsiConsole.cs @@ -16,12 +16,13 @@ namespace Spectre.Console ColorSystem = ColorSystemSupport.Detect, Out = System.Console.Out, }); + Initialize(System.Console.Out); Created = true; return console; }); /// - /// Gets the current renderer. + /// Gets the underlying . /// public static IAnsiConsole Console => _console.Value; @@ -30,8 +31,6 @@ namespace Spectre.Console /// public static Capabilities Capabilities => Console.Capabilities; - internal static bool Created { get; private set; } - /// /// Gets the buffer width of the console. /// @@ -48,33 +47,6 @@ namespace Spectre.Console get => Console.Height; } - /// - /// Gets or sets the foreground color. - /// - public static Color Foreground - { - get => Console.Foreground; - set => Console.SetColor(value, true); - } - - /// - /// Gets or sets the background color. - /// - public static Color Background - { - get => Console.Background; - set => Console.SetColor(value, false); - } - - /// - /// Gets or sets the text decoration. - /// - public static Decoration Decoration - { - get => Console.Decoration; - set => Console.Decoration = value; - } - /// /// Creates a new instance /// from the provided settings. @@ -85,29 +57,5 @@ namespace Spectre.Console { return ConsoleBuilder.Build(settings); } - - /// - /// Resets colors and text decorations. - /// - public static void Reset() - { - Console.Reset(); - } - - /// - /// Resets the current applied text decorations. - /// - public static void ResetDecoration() - { - Console.ResetDecoration(); - } - - /// - /// Resets the current applied foreground and background colors. - /// - public static void ResetColors() - { - Console.ResetColors(); - } } } diff --git a/src/Spectre.Console/Capabilities.cs b/src/Spectre.Console/Capabilities.cs index 7de2932..284999c 100644 --- a/src/Spectre.Console/Capabilities.cs +++ b/src/Spectre.Console/Capabilities.cs @@ -11,6 +11,17 @@ namespace Spectre.Console /// public bool SupportsAnsi { get; } + /// + /// Gets a value indicating whether or not + /// the console support links. + /// + /// + /// There is probably a lot of room for improvement here + /// once we have more information about the terminal + /// we're running inside. + /// + public bool SupportLinks => SupportsAnsi && !LegacyConsole; + /// /// Gets the color system. /// diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Rendering.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Rendering.cs index 59ec1e1..dec7b3c 100644 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Rendering.cs +++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Rendering.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Spectre.Console.Internal; using Spectre.Console.Rendering; namespace Spectre.Console @@ -28,30 +27,18 @@ namespace Spectre.Console } var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole); + var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray(); + segments = Segment.Merge(segments).ToArray(); - using (console.PushStyle(Style.Plain)) + var current = Style.Plain; + foreach (var segment in segments) { - var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray(); - segments = Segment.Merge(segments).ToArray(); - - var current = Style.Plain; - foreach (var segment in segments) + if (string.IsNullOrEmpty(segment.Text)) { - if (string.IsNullOrEmpty(segment.Text)) - { - continue; - } - - if (!segment.Style.Equals(current)) - { - console.Foreground = segment.Style.Foreground; - console.Background = segment.Style.Background; - console.Decoration = segment.Style.Decoration; - current = segment.Style; - } - - console.Write(segment.Text); + continue; } + + console.Write(segment.Text, segment.Style); } } } diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Write.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Write.cs deleted file mode 100644 index e93ccfc..0000000 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Write.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.Globalization; - -namespace Spectre.Console -{ - /// - /// Contains extension methods for . - /// - public static partial class AnsiConsoleExtensions - { - /// - /// Writes the specified string value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, string value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (value != null) - { - console.Write(value); - } - } - - /// - /// Writes the text representation of the specified 32-bit - /// signed integer value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, int value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 32-bit - /// signed integer value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, int value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 32-bit - /// unsigned integer value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, uint value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 32-bit - /// unsigned integer value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, uint value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 64-bit - /// signed integer value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, long value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 64-bit - /// signed integer value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, long value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 64-bit - /// unsigned integer value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, ulong value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 64-bit - /// unsigned integer value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, ulong value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified single-precision - /// floating-point value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, float value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified single-precision - /// floating-point value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, float value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified double-precision - /// floating-point value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, double value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified double-precision - /// floating-point value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, double value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified decimal value, to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, decimal value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified decimal value, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, decimal value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - Write(console, value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified boolean value to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, bool value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified boolean value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, bool value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - Write(console, value.ToString(provider)); - } - - /// - /// Writes the specified Unicode character to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, char value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the specified Unicode character to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, char value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - Write(console, value.ToString(provider)); - } - - /// - /// Writes the specified array of Unicode characters to the console. - /// - /// The console to write to. - /// The value to write. - public static void Write(this IAnsiConsole console, char[] value) - { - Write(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the specified array of Unicode characters to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, char[] value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - - for (var index = 0; index < value.Length; index++) - { - console.Write(value[index].ToString(provider)); - } - } - - /// - /// Writes the text representation of the specified array of objects, - /// to the console using the specified format information. - /// - /// The console to write to. - /// A composite format string. - /// An array of objects to write. - public static void Write(this IAnsiConsole console, string format, params object[] args) - { - Write(console, CultureInfo.CurrentCulture, format, args); - } - - /// - /// Writes the text representation of the specified array of objects, - /// to the console using the specified format information. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// A composite format string. - /// An array of objects to write. - public static void Write(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - Write(console, string.Format(provider, format, args)); - } - } -} diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.WriteLine.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.WriteLine.cs deleted file mode 100644 index 8a003b2..0000000 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.WriteLine.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System; -using System.Globalization; - -namespace Spectre.Console -{ - /// - /// Contains extension methods for . - /// - public static partial class AnsiConsoleExtensions - { - /// - /// Writes an empty line to the console. - /// - /// The console to write to. - public static void WriteLine(this IAnsiConsole console) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Write(Environment.NewLine); - } - - /// - /// Writes the specified string value, followed by the - /// current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, string value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (value != null) - { - console.Write(value); - } - - console.WriteLine(); - } - - /// - /// Writes the text representation of the specified 32-bit signed integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, int value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 32-bit signed integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, int value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 32-bit unsigned integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, uint value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 32-bit unsigned integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, uint value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 64-bit signed integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, long value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 64-bit signed integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, long value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified 64-bit unsigned integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, ulong value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified 64-bit unsigned integer value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, ulong value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified single-precision floating-point - /// value, followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, float value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified single-precision floating-point - /// value, followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, float value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified double-precision floating-point - /// value, followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, double value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified double-precision floating-point - /// value, followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, double value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.WriteLine(value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified decimal value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, decimal value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified decimal value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, decimal value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - WriteLine(console, value.ToString(provider)); - } - - /// - /// Writes the text representation of the specified boolean value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, bool value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the text representation of the specified boolean value, - /// followed by the current line terminator, to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, bool value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - WriteLine(console, value.ToString(provider)); - } - - /// - /// Writes the specified Unicode character, followed by the current - /// line terminator, value to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, char value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the specified Unicode character, followed by the current - /// line terminator, value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, char value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - WriteLine(console, value.ToString(provider)); - } - - /// - /// Writes the specified array of Unicode characters, followed by the current - /// line terminator, value to the console. - /// - /// The console to write to. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, char[] value) - { - WriteLine(console, CultureInfo.CurrentCulture, value); - } - - /// - /// Writes the specified array of Unicode characters, followed by the current - /// line terminator, value to the console. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// The value to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, char[] value) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - - for (var index = 0; index < value.Length; index++) - { - console.Write(value[index].ToString(provider)); - } - - console.WriteLine(); - } - - /// - /// Writes the text representation of the specified array of objects, - /// followed by the current line terminator, to the console - /// using the specified format information. - /// - /// The console to write to. - /// A composite format string. - /// An array of objects to write. - public static void WriteLine(this IAnsiConsole console, string format, params object[] args) - { - WriteLine(console, CultureInfo.CurrentCulture, format, args); - } - - /// - /// Writes the text representation of the specified array of objects, - /// followed by the current line terminator, to the console - /// using the specified format information. - /// - /// The console to write to. - /// An object that supplies culture-specific formatting information. - /// A composite format string. - /// An array of objects to write. - public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - WriteLine(console, string.Format(provider, format, args)); - } - } -} diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs index 3614320..b8f659a 100644 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs +++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs @@ -8,47 +8,34 @@ namespace Spectre.Console public static partial class AnsiConsoleExtensions { /// - /// Resets colors and text decorations. + /// Writes an empty line to the console. /// - /// The console to reset. - public static void Reset(this IAnsiConsole console) + /// The console to write to. + public static void WriteLine(this IAnsiConsole console) { if (console is null) { throw new ArgumentNullException(nameof(console)); } - console.ResetColors(); - console.ResetDecoration(); + console.Write(Environment.NewLine, Style.Plain); } /// - /// Resets the current applied text decorations. + /// Writes the specified string value, followed by the current line terminator, to the console. /// - /// The console to reset the text decorations for. - public static void ResetDecoration(this IAnsiConsole console) + /// The console to write to. + /// The text to write. + /// The text style. + public static void WriteLine(this IAnsiConsole console, string text, Style style) { if (console is null) { throw new ArgumentNullException(nameof(console)); } - console.Decoration = Decoration.None; - } - - /// - /// Resets the current applied foreground and background colors. - /// - /// The console to reset colors for. - public static void ResetColors(this IAnsiConsole console) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - console.Foreground = Color.Default; - console.Background = Color.Default; + console.Write(text, style); + console.WriteLine(); } } } diff --git a/src/Spectre.Console/Extensions/StyleExtensions.cs b/src/Spectre.Console/Extensions/StyleExtensions.cs index e11df9b..13cf29b 100644 --- a/src/Spectre.Console/Extensions/StyleExtensions.cs +++ b/src/Spectre.Console/Extensions/StyleExtensions.cs @@ -66,5 +66,26 @@ namespace Spectre.Console background: style.Background, decoration: decoration); } + + /// + /// Creates a new style from the specified one with + /// the specified link. + /// + /// The style. + /// The link. + /// The same instance so that multiple calls can be chained. + public static Style WithLink(this Style style, string link) + { + if (style is null) + { + throw new ArgumentNullException(nameof(style)); + } + + return new Style( + foreground: style.Foreground, + background: style.Background, + decoration: style.Decoration, + link: link); + } } } diff --git a/src/Spectre.Console/IAnsiConsole.cs b/src/Spectre.Console/IAnsiConsole.cs index 14e97b1..2b6e064 100644 --- a/src/Spectre.Console/IAnsiConsole.cs +++ b/src/Spectre.Console/IAnsiConsole.cs @@ -27,25 +27,11 @@ namespace Spectre.Console /// Encoding Encoding { get; } - /// - /// Gets or sets the current text decoration. - /// - Decoration Decoration { get; set; } - - /// - /// Gets or sets the current foreground. - /// - Color Foreground { get; set; } - - /// - /// Gets or sets the current background. - /// - Color Background { get; set; } - /// /// Writes a string followed by a line terminator to the console. /// /// The string to write. - void Write(string text); + /// The style to use. + void Write(string text, Style style); } } diff --git a/src/Spectre.Console/Internal/Ansi/AnsiBuilder.cs b/src/Spectre.Console/Internal/Ansi/AnsiBuilder.cs index abdf13f..b38c92a 100644 --- a/src/Spectre.Console/Internal/Ansi/AnsiBuilder.cs +++ b/src/Spectre.Console/Internal/Ansi/AnsiBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; namespace Spectre.Console.Internal @@ -5,40 +6,59 @@ namespace Spectre.Console.Internal internal static class AnsiBuilder { public static string GetAnsi( - ColorSystem system, + Capabilities capabilities, string text, Decoration decoration, Color foreground, - Color background) + Color background, + string? link) { var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration); // Got foreground? if (foreground != Color.Default) { - codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, foreground, foreground: true)); + codes = codes.Concat( + AnsiColorBuilder.GetAnsiCodes( + capabilities.ColorSystem, + foreground, + true)); } // Got background? if (background != Color.Default) { - codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, background, foreground: false)); + codes = codes.Concat( + AnsiColorBuilder.GetAnsiCodes( + capabilities.ColorSystem, + background, + false)); } var result = codes.ToArray(); - if (result.Length == 0) + if (result.Length == 0 && link == null) { return text; } - var lol = string.Concat( - "\u001b[", - string.Join(";", result), - "m", - text, - "\u001b[0m"); + var ansiCodes = string.Join(";", result); + var ansi = result.Length > 0 + ? $"\u001b[{ansiCodes}m{text}\u001b[0m" + : text; - return lol; + if (link != null && !capabilities.LegacyConsole) + { + // Empty links means we should take the URL from the text. + if (link.Equals(Constants.EmptyLink, StringComparison.Ordinal)) + { + link = text; + } + + var linkId = Math.Abs(link.GetDeterministicHashCode()); + ansi = $"\u001b]8;id={linkId};{link}\u001b\\{ansi}\u001b]8;;\u001b\\"; + } + + return ansi; } } } diff --git a/src/Spectre.Console/Internal/AnsiConsoleRenderer.cs b/src/Spectre.Console/Internal/AnsiConsoleRenderer.cs index 840a9c7..792d57b 100644 --- a/src/Spectre.Console/Internal/AnsiConsoleRenderer.cs +++ b/src/Spectre.Console/Internal/AnsiConsoleRenderer.cs @@ -7,13 +7,9 @@ namespace Spectre.Console.Internal internal sealed class AnsiConsoleRenderer : IAnsiConsole { private readonly TextWriter _out; - private readonly ColorSystem _system; public Capabilities Capabilities { get; } public Encoding Encoding { get; } - public Decoration Decoration { get; set; } - public Color Foreground { get; set; } - public Color Background { get; set; } public int Width { @@ -44,28 +40,26 @@ namespace Spectre.Console.Internal public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole) { _out = @out ?? throw new ArgumentNullException(nameof(@out)); - _system = system; Capabilities = new Capabilities(true, system, legacyConsole); Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8; - Foreground = Color.Default; - Background = Color.Default; - Decoration = Decoration.None; } - public void Write(string text) + public void Write(string text, Style style) { if (string.IsNullOrEmpty(text)) { return; } + style ??= Style.Plain; + var parts = text.NormalizeLineEndings().Split(new[] { '\n' }); foreach (var (_, _, last, part) in parts.Enumerate()) { if (!string.IsNullOrEmpty(part)) { - _out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background)); + _out.Write(AnsiBuilder.GetAnsi(Capabilities, part, style.Decoration, style.Foreground, style.Background, style.Link)); } if (!last) diff --git a/src/Spectre.Console/Internal/ConsoleBuilder.cs b/src/Spectre.Console/Internal/ConsoleBuilder.cs index 453f2ac..a076717 100644 --- a/src/Spectre.Console/Internal/ConsoleBuilder.cs +++ b/src/Spectre.Console/Internal/ConsoleBuilder.cs @@ -56,10 +56,7 @@ namespace Spectre.Console.Internal if (supportsAnsi) { - return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole) - { - Decoration = Decoration.None, - }; + return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole); } return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole); diff --git a/src/Spectre.Console/Internal/Constants.cs b/src/Spectre.Console/Internal/Constants.cs index 76a2cf0..309a8ea 100644 --- a/src/Spectre.Console/Internal/Constants.cs +++ b/src/Spectre.Console/Internal/Constants.cs @@ -4,5 +4,7 @@ namespace Spectre.Console.Internal { public const int DefaultBufferWidth = 80; public const int DefaultBufferHeight = 9001; + + public const string EmptyLink = "https://emptylink"; } } diff --git a/src/Spectre.Console/Internal/Extensions/AnsiConsoleExtensions.cs b/src/Spectre.Console/Internal/Extensions/AnsiConsoleExtensions.cs deleted file mode 100644 index 05bf631..0000000 --- a/src/Spectre.Console/Internal/Extensions/AnsiConsoleExtensions.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Spectre.Console.Internal -{ - internal static class AnsiConsoleExtensions - { - public static IDisposable PushStyle(this IAnsiConsole console, Style style) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - 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) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - var current = foreground ? console.Foreground : console.Background; - console.SetColor(color, foreground); - return new ColorScope(console, current, foreground); - } - - public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - var current = console.Decoration; - console.Decoration = decoration; - return new DecorationScope(console, current); - } - - public static void SetColor(this IAnsiConsole console, Color color, bool foreground) - { - if (console is null) - { - throw new ArgumentNullException(nameof(console)); - } - - if (foreground) - { - console.Foreground = color; - } - else - { - console.Background = color; - } - } - } - - internal sealed class StyleScope : IDisposable - { - private readonly IAnsiConsole _console; - private readonly Style _style; - - public StyleScope(IAnsiConsole console, Style style) - { - _console = console ?? throw new ArgumentNullException(nameof(console)); - _style = style ?? throw new ArgumentNullException(nameof(style)); - } - - [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] - [SuppressMessage("Performance", "CA1821:Remove empty Finalizers")] - ~StyleScope() - { - throw new InvalidOperationException("Style scope was not disposed."); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _console.SetColor(_style.Foreground, true); - _console.SetColor(_style.Background, false); - _console.Decoration = _style.Decoration; - } - } - - internal sealed class ColorScope : IDisposable - { - private readonly IAnsiConsole _console; - private readonly Color _color; - private readonly bool _foreground; - - public ColorScope(IAnsiConsole console, Color color, bool foreground) - { - _console = console ?? throw new ArgumentNullException(nameof(console)); - _color = color; - _foreground = foreground; - } - - [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] - [SuppressMessage("Performance", "CA1821:Remove empty Finalizers")] - ~ColorScope() - { - throw new InvalidOperationException("Color scope was not disposed."); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _console.SetColor(_color, _foreground); - } - } - - internal sealed class DecorationScope : IDisposable - { - private readonly IAnsiConsole _console; - private readonly Decoration _decoration; - - public DecorationScope(IAnsiConsole console, Decoration decoration) - { - _console = console ?? throw new ArgumentNullException(nameof(console)); - _decoration = decoration; - } - - [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] - [SuppressMessage("Performance", "CA1821:Remove empty Finalizers")] - ~DecorationScope() - { - throw new InvalidOperationException("Decoration scope was not disposed."); - } - - public void Dispose() - { - GC.SuppressFinalize(this); - _console.Decoration = _decoration; - } - } -} diff --git a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs index 0b4a495..567be83 100644 --- a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs +++ b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs @@ -78,5 +78,28 @@ namespace Spectre.Console.Internal return result.ToArray(); } + + // https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/ + public static int GetDeterministicHashCode(this string str) + { + unchecked + { + var hash1 = (5381 << 16) + 5381; + var hash2 = hash1; + + for (var i = 0; i < str.Length; i += 2) + { + hash1 = ((hash1 << 5) + hash1) ^ str[i]; + if (i == str.Length - 1) + { + break; + } + + hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; + } + + return hash1 + (hash2 * 1566083941); + } + } } } diff --git a/src/Spectre.Console/Internal/FallbackConsoleRenderer.cs b/src/Spectre.Console/Internal/FallbackConsoleRenderer.cs index 1775744..a5ac093 100644 --- a/src/Spectre.Console/Internal/FallbackConsoleRenderer.cs +++ b/src/Spectre.Console/Internal/FallbackConsoleRenderer.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Text; @@ -6,16 +5,11 @@ namespace Spectre.Console.Internal { internal sealed class FallbackConsoleRenderer : IAnsiConsole { - private readonly ConsoleColor _defaultForeground; - private readonly ConsoleColor _defaultBackground; - private readonly TextWriter _out; private readonly ColorSystem _system; - private ConsoleColor _foreground; - private ConsoleColor _background; + private Style? _lastStyle; public Capabilities Capabilities { get; } - public Encoding Encoding { get; } public int Width @@ -44,47 +38,6 @@ namespace Spectre.Console.Internal } } - public Decoration Decoration { get; set; } - - public Color Foreground - { - get => _foreground; - set - { - _foreground = Color.ToConsoleColor(value); - if (_system != ColorSystem.NoColors && _out.IsStandardOut()) - { - if ((int)_foreground == -1) - { - _foreground = _defaultForeground; - } - - System.Console.ForegroundColor = _foreground; - } - } - } - - public Color Background - { - get => _background; - set - { - _background = Color.ToConsoleColor(value); - if (_system != ColorSystem.NoColors && _out.IsStandardOut()) - { - if ((int)_background == -1) - { - _background = _defaultBackground; - } - - if (_system != ColorSystem.NoColors) - { - System.Console.BackgroundColor = _background; - } - } - } - } - public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole) { _out = @out; @@ -92,25 +45,46 @@ namespace Spectre.Console.Internal if (_out.IsStandardOut()) { - _defaultForeground = System.Console.ForegroundColor; - _defaultBackground = System.Console.BackgroundColor; - Encoding = System.Console.OutputEncoding; } else { - _defaultForeground = ConsoleColor.Gray; - _defaultBackground = ConsoleColor.Black; - Encoding = Encoding.UTF8; } Capabilities = new Capabilities(false, _system, legacyConsole); } - public void Write(string text) + public void Write(string text, Style style) { + if (_lastStyle?.Equals(style) != true) + { + SetStyle(style); + } + _out.Write(text.NormalizeLineEndings(native: true)); } + + private void SetStyle(Style style) + { + _lastStyle = style; + + if (_out.IsStandardOut()) + { + System.Console.ResetColor(); + + var background = Color.ToConsoleColor(style.Background); + if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)background != -1) + { + System.Console.BackgroundColor = background; + } + + var foreground = Color.ToConsoleColor(style.Foreground); + if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)foreground != -1) + { + System.Console.ForegroundColor = foreground; + } + } + } } } diff --git a/src/Spectre.Console/Internal/Text/StyleParser.cs b/src/Spectre.Console/Internal/Text/StyleParser.cs index 184880b..2be1e53 100644 --- a/src/Spectre.Console/Internal/Text/StyleParser.cs +++ b/src/Spectre.Console/Internal/Text/StyleParser.cs @@ -35,6 +35,7 @@ namespace Spectre.Console.Internal var effectiveDecoration = (Decoration?)null; var effectiveForeground = (Color?)null; var effectiveBackground = (Color?)null; + var effectiveLink = (string?)null; var parts = text.Split(new[] { ' ' }); var foreground = true; @@ -51,6 +52,23 @@ namespace Spectre.Console.Internal continue; } + if (part.StartsWith("link=", StringComparison.OrdinalIgnoreCase)) + { + if (effectiveLink != null) + { + error = "A link has already been set."; + return null; + } + + effectiveLink = part.Substring(5); + continue; + } + else if (part.StartsWith("link", StringComparison.OrdinalIgnoreCase)) + { + effectiveLink = Constants.EmptyLink; + continue; + } + var decoration = DecorationTable.GetDecoration(part); if (decoration != null) { @@ -116,7 +134,11 @@ namespace Spectre.Console.Internal } error = null; - return new Style(effectiveForeground, effectiveBackground, effectiveDecoration); + return new Style( + effectiveForeground, + effectiveBackground, + effectiveDecoration, + effectiveLink); } [SuppressMessage("Design", "CA1031:Do not catch general exception types")] diff --git a/src/Spectre.Console/Spectre.Console.csproj b/src/Spectre.Console/Spectre.Console.csproj index 140c0f2..2464963 100644 --- a/src/Spectre.Console/Spectre.Console.csproj +++ b/src/Spectre.Console/Spectre.Console.csproj @@ -41,12 +41,6 @@ AnsiConsoleExtensions.cs - - AnsiConsoleExtensions.cs - - - AnsiConsoleExtensions.cs - 3.0.0 diff --git a/src/Spectre.Console/Style.cs b/src/Spectre.Console/Style.cs index 9cc10be..4eb7bad 100644 --- a/src/Spectre.Console/Style.cs +++ b/src/Spectre.Console/Style.cs @@ -24,6 +24,11 @@ namespace Spectre.Console /// public Decoration Decoration { get; } + /// + /// Gets the link. + /// + public string? Link { get; } + /// /// Gets an with the /// default colors and without text decoration. @@ -31,7 +36,7 @@ namespace Spectre.Console public static Style Plain { get; } = new Style(); private Style() - : this(null, null, null) + : this(null, null, null, null) { } @@ -41,11 +46,13 @@ namespace Spectre.Console /// The foreground color. /// The background color. /// The text decoration. - public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null) + /// The link. + public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null, string? link = null) { Foreground = foreground ?? Color.Default; Background = background ?? Color.Default; Decoration = decoration ?? Decoration.None; + Link = link; } /// @@ -78,13 +85,23 @@ namespace Spectre.Console return new Style(decoration: decoration); } + /// + /// Creates a new style from the specified link. + /// + /// The link. + /// A new with the specified link. + public static Style WithLink(string link) + { + return new Style(link: link); + } + /// /// Creates a copy of the current . /// /// A copy of the current . public Style Clone() { - return new Style(Foreground, Background, Decoration); + return new Style(Foreground, Background, Decoration, Link); } /// @@ -111,7 +128,13 @@ namespace Spectre.Console background = other.Background; } - return new Style(foreground, background, Decoration | other.Decoration); + var link = Link; + if (!string.IsNullOrWhiteSpace(other.Link)) + { + link = other.Link; + } + + return new Style(foreground, background, Decoration | other.Decoration, link); } /// @@ -158,6 +181,12 @@ namespace Spectre.Console hash = (hash * 16777619) ^ Foreground.GetHashCode(); hash = (hash * 16777619) ^ Background.GetHashCode(); hash = (hash * 16777619) ^ Decoration.GetHashCode(); + + if (Link != null) + { + hash = (hash * 16777619) ^ Link?.GetHashCode() ?? 0; + } + return hash; } } @@ -178,7 +207,8 @@ namespace Spectre.Console return Foreground.Equals(other.Foreground) && Background.Equals(other.Background) && - Decoration == other.Decoration; + Decoration == other.Decoration && + string.Equals(Link, other.Link, StringComparison.Ordinal); } } }