diff --git a/src/Spectre.Console.Testing/TestConsoleExtensions.cs b/src/Spectre.Console.Testing/TestConsoleExtensions.cs index 59d9c1a..eb3f441 100644 --- a/src/Spectre.Console.Testing/TestConsoleExtensions.cs +++ b/src/Spectre.Console.Testing/TestConsoleExtensions.cs @@ -17,6 +17,18 @@ namespace Spectre.Console.Testing return console; } + /// + /// Sets whether or not ANSI is supported. + /// + /// The console. + /// Whether or not VT/ANSI control codes are supported. + /// The same instance so that multiple calls can be chained. + public static TestConsole SupportsAnsi(this TestConsole console, bool enable) + { + console.Profile.Capabilities.Ansi = enable; + return console; + } + /// /// Makes the console interactive. /// diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs new file mode 100644 index 0000000..3d23c64 --- /dev/null +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs @@ -0,0 +1,47 @@ +using Shouldly; +using Spectre.Console.Advanced; +using Spectre.Console.Testing; +using Xunit; + +namespace Spectre.Console.Tests.Unit +{ + public partial class AnsiConsoleTests + { + public sealed class Advanced + { + [Fact] + public void Should_Write_Ansi_Codes_To_Console_If_Supported() + { + // Given + var console = new TestConsole() + .SupportsAnsi(true) + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); + + // When + console.WriteAnsi("Hello"); + + // Then + console.Output.NormalizeLineEndings() + .ShouldBe("Hello"); + } + + [Fact] + public void Should_Not_Write_Ansi_Codes_To_Console_If_Not_Supported() + { + // Given + var console = new TestConsole() + .SupportsAnsi(false) + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); + + // When + console.WriteAnsi("Hello"); + + // Then + console.Output.NormalizeLineEndings() + .ShouldBeEmpty(); + } + } + } +} diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs index fc06cfb..528b87e 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.cs @@ -7,103 +7,109 @@ namespace Spectre.Console.Tests.Unit { public partial class AnsiConsoleTests { - [Theory] - [InlineData(false, "HelloWorld")] - [InlineData(true, "HelloWorld")] - public void Should_Clear_Screen(bool home, string expected) + public sealed class Clear { - // Given - var console = new TestConsole() - .Colors(ColorSystem.Standard) - .EmitAnsiSequences(); + [Theory] + [InlineData(false, "HelloWorld")] + [InlineData(true, "HelloWorld")] + public void Should_Clear_Screen(bool home, string expected) + { + // Given + var console = new TestConsole() + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); - // When - console.Write("Hello"); - console.Clear(home); - console.Write("World"); + // When + console.Write("Hello"); + console.Clear(home); + console.Write("World"); - // Then - console.Output.ShouldBe(expected); + // Then + console.Output.ShouldBe(expected); + } } - [Fact] - public void Should_Combine_Decoration_And_Colors() + public sealed class Write { - // Given - var console = new TestConsole() - .Colors(ColorSystem.Standard) - .EmitAnsiSequences(); + [Fact] + public void Should_Combine_Decoration_And_Colors() + { + // Given + var console = new TestConsole() + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); - // When - console.Write( - "Hello", - new Style() - .Foreground(Color.RoyalBlue1) - .Background(Color.NavajoWhite1) - .Decoration(Decoration.Italic)); + // When + console.Write( + "Hello", + new Style() + .Foreground(Color.RoyalBlue1) + .Background(Color.NavajoWhite1) + .Decoration(Decoration.Italic)); - // Then - console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); - } + // Then + console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); + } - [Fact] - public void Should_Not_Include_Foreground_If_Set_To_Default_Color() - { - // Given - var console = new TestConsole() - .Colors(ColorSystem.Standard) - .EmitAnsiSequences(); + [Fact] + public void Should_Not_Include_Foreground_If_Set_To_Default_Color() + { + // Given + var console = new TestConsole() + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); - // When - console.Write( - "Hello", - new Style() - .Foreground(Color.Default) - .Background(Color.NavajoWhite1) - .Decoration(Decoration.Italic)); + // When + console.Write( + "Hello", + new Style() + .Foreground(Color.Default) + .Background(Color.NavajoWhite1) + .Decoration(Decoration.Italic)); - // Then - console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); - } + // Then + console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); + } - [Fact] - public void Should_Not_Include_Background_If_Set_To_Default_Color() - { - // Given - var console = new TestConsole() - .Colors(ColorSystem.Standard) - .EmitAnsiSequences(); + [Fact] + public void Should_Not_Include_Background_If_Set_To_Default_Color() + { + // Given + var console = new TestConsole() + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); - // When - console.Write( - "Hello", - new Style() - .Foreground(Color.RoyalBlue1) - .Background(Color.Default) - .Decoration(Decoration.Italic)); + // When + console.Write( + "Hello", + new Style() + .Foreground(Color.RoyalBlue1) + .Background(Color.Default) + .Decoration(Decoration.Italic)); - // Then - console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); - } + // Then + console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); + } - [Fact] - public void Should_Not_Include_Decoration_If_Set_To_None() - { - // Given - var console = new TestConsole() - .Colors(ColorSystem.Standard) - .EmitAnsiSequences(); + [Fact] + public void Should_Not_Include_Decoration_If_Set_To_None() + { + // Given + var console = new TestConsole() + .Colors(ColorSystem.Standard) + .EmitAnsiSequences(); - // When - console.Write( - "Hello", - new Style() - .Foreground(Color.RoyalBlue1) - .Background(Color.NavajoWhite1) - .Decoration(Decoration.None)); + // When + console.Write( + "Hello", + new Style() + .Foreground(Color.RoyalBlue1) + .Background(Color.NavajoWhite1) + .Decoration(Decoration.None)); - // Then - console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); + // Then + console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); + } } public sealed class WriteLine diff --git a/src/Spectre.Console/Capabilities.cs b/src/Spectre.Console/Capabilities.cs index 2c367b8..3419640 100644 --- a/src/Spectre.Console/Capabilities.cs +++ b/src/Spectre.Console/Capabilities.cs @@ -16,7 +16,7 @@ namespace Spectre.Console /// /// Gets or sets a value indicating whether or not - /// the console supports Ansi. + /// the console supports VT/ANSI control codes. /// public bool Ansi { get; set; } diff --git a/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs b/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs new file mode 100644 index 0000000..cf54473 --- /dev/null +++ b/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs @@ -0,0 +1,28 @@ +using System; + +namespace Spectre.Console.Advanced +{ + /// + /// Contains extension methods for . + /// + public static class AnsiConsoleExtensions + { + /// + /// Writes a VT/Ansi control code sequence to the console (if supported). + /// + /// The console to write to. + /// The VT/Ansi control code sequence to write. + public static void WriteAnsi(this IAnsiConsole console, string sequence) + { + if (console is null) + { + throw new ArgumentNullException(nameof(console)); + } + + if (console.Profile.Capabilities.Ansi) + { + console.Write(new ControlCode(sequence)); + } + } + } +} diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs index e57b4df..b91b813 100644 --- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs +++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.cs @@ -38,6 +38,11 @@ namespace Spectre.Console /// The text to write. public static void Write(this IAnsiConsole console, string text) { + if (console is null) + { + throw new ArgumentNullException(nameof(console)); + } + console.Write(new Text(text, Style.Plain)); } @@ -49,6 +54,11 @@ namespace Spectre.Console /// The text style. public static void Write(this IAnsiConsole console, string text, Style style) { + if (console is null) + { + throw new ArgumentNullException(nameof(console)); + } + console.Write(new Text(text, style)); } diff --git a/src/Spectre.Console/IReadOnlyCapabilities.cs b/src/Spectre.Console/IReadOnlyCapabilities.cs index 340f338..75380ac 100644 --- a/src/Spectre.Console/IReadOnlyCapabilities.cs +++ b/src/Spectre.Console/IReadOnlyCapabilities.cs @@ -14,7 +14,7 @@ namespace Spectre.Console /// /// Gets a value indicating whether or not - /// the console supports Ansi. + /// the console supports VT/ANSI control codes. /// bool Ansi { get; } diff --git a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs index 72addb5..e278393 100644 --- a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs +++ b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs @@ -22,12 +22,12 @@ namespace Spectre.Console public void Clear(bool home) { - Write(new ControlSequence(ED(2))); - Write(new ControlSequence(ED(3))); + Write(new ControlCode(ED(2))); + Write(new ControlCode(ED(3))); if (home) { - Write(new ControlSequence(CUP(1, 1))); + Write(new ControlCode(CUP(1, 1))); } } diff --git a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleCursor.cs b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleCursor.cs index 9c6aa8c..4fbb8f8 100644 --- a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleCursor.cs +++ b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleCursor.cs @@ -16,11 +16,11 @@ namespace Spectre.Console { if (show) { - _backend.Write(new ControlSequence(SM(DECTCEM))); + _backend.Write(new ControlCode(SM(DECTCEM))); } else { - _backend.Write(new ControlSequence(RM(DECTCEM))); + _backend.Write(new ControlCode(RM(DECTCEM))); } } @@ -34,23 +34,23 @@ namespace Spectre.Console switch (direction) { case CursorDirection.Up: - _backend.Write(new ControlSequence(CUU(steps))); + _backend.Write(new ControlCode(CUU(steps))); break; case CursorDirection.Down: - _backend.Write(new ControlSequence(CUD(steps))); + _backend.Write(new ControlCode(CUD(steps))); break; case CursorDirection.Right: - _backend.Write(new ControlSequence(CUF(steps))); + _backend.Write(new ControlCode(CUF(steps))); break; case CursorDirection.Left: - _backend.Write(new ControlSequence(CUB(steps))); + _backend.Write(new ControlCode(CUB(steps))); break; } } public void SetPosition(int column, int line) { - _backend.Write(new ControlSequence(CUP(line, column))); + _backend.Write(new ControlCode(CUP(line, column))); } } } diff --git a/src/Spectre.Console/Rendering/LiveRenderable.cs b/src/Spectre.Console/Rendering/LiveRenderable.cs index 582f16a..e3199c9 100644 --- a/src/Spectre.Console/Rendering/LiveRenderable.cs +++ b/src/Spectre.Console/Rendering/LiveRenderable.cs @@ -25,11 +25,11 @@ namespace Spectre.Console.Rendering { if (_shape == null) { - return new ControlSequence(string.Empty); + return new ControlCode(string.Empty); } var linesToMoveUp = _shape.Value.Height - 1; - return new ControlSequence("\r" + CUU(linesToMoveUp)); + return new ControlCode("\r" + CUU(linesToMoveUp)); } } @@ -39,11 +39,11 @@ namespace Spectre.Console.Rendering { if (_shape == null) { - return new ControlSequence(string.Empty); + return new ControlCode(string.Empty); } var linesToClear = _shape.Value.Height - 1; - return new ControlSequence("\r" + EL(2) + (CUU(1) + EL(2)).Repeat(linesToClear)); + return new ControlCode("\r" + EL(2) + (CUU(1) + EL(2)).Repeat(linesToClear)); } } diff --git a/src/Spectre.Console/Rendering/RenderContext.cs b/src/Spectre.Console/Rendering/RenderContext.cs index b4ffff1..be18254 100644 --- a/src/Spectre.Console/Rendering/RenderContext.cs +++ b/src/Spectre.Console/Rendering/RenderContext.cs @@ -14,6 +14,11 @@ namespace Spectre.Console.Rendering /// public ColorSystem ColorSystem => _capabilities.ColorSystem; + /// + /// Gets a value indicating whether or not VT/Ansi codes are supported. + /// + public bool Ansi => _capabilities.Ansi; + /// /// Gets a value indicating whether or not unicode is supported. /// diff --git a/src/Spectre.Console/Widgets/ControlSequence.cs b/src/Spectre.Console/Widgets/ControlCode.cs similarity index 71% rename from src/Spectre.Console/Widgets/ControlSequence.cs rename to src/Spectre.Console/Widgets/ControlCode.cs index a577f15..45c4398 100644 --- a/src/Spectre.Console/Widgets/ControlSequence.cs +++ b/src/Spectre.Console/Widgets/ControlCode.cs @@ -3,11 +3,11 @@ using Spectre.Console.Rendering; namespace Spectre.Console { - internal sealed class ControlSequence : Renderable + internal sealed class ControlCode : Renderable { private readonly Segment _segment; - public ControlSequence(string control) + public ControlCode(string control) { _segment = Segment.Control(control); } @@ -19,7 +19,10 @@ namespace Spectre.Console protected override IEnumerable Render(RenderContext context, int maxWidth) { - yield return _segment; + if (context.Ansi) + { + yield return _segment; + } } } } diff --git a/src/Spectre.Console/Widgets/Progress/ProgressContext.cs b/src/Spectre.Console/Widgets/Progress/ProgressContext.cs index b7dcf41..9b9510f 100644 --- a/src/Spectre.Console/Widgets/Progress/ProgressContext.cs +++ b/src/Spectre.Console/Widgets/Progress/ProgressContext.cs @@ -73,7 +73,7 @@ namespace Spectre.Console public void Refresh() { _renderer.Update(this); - _console.Write(new ControlSequence(string.Empty)); + _console.Write(new ControlCode(string.Empty)); } internal IReadOnlyList GetTasks() diff --git a/src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs b/src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs index 93cd7f2..13a327d 100644 --- a/src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs +++ b/src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs @@ -38,7 +38,7 @@ namespace Spectre.Console public void Redraw() { - _console.Write(new ControlSequence(string.Empty)); + _console.Write(new ControlCode(string.Empty)); } public bool Update(ConsoleKey key)