Add method to write VT/ANSI control codes

Adds a new extension method IAnsiConsole.WriteAnsi that writes
VT/ANSI control codes to the console. If VT/ANSI control codes are
not supported, nothing will be written.
This commit is contained in:
Patrik Svensson 2021-04-22 22:11:21 +02:00 committed by Phil Scott
parent 91f910326c
commit 3463dde543
14 changed files with 212 additions and 101 deletions

View File

@ -17,6 +17,18 @@ namespace Spectre.Console.Testing
return console; return console;
} }
/// <summary>
/// Sets whether or not ANSI is supported.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="enable">Whether or not VT/ANSI control codes are supported.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole SupportsAnsi(this TestConsole console, bool enable)
{
console.Profile.Capabilities.Ansi = enable;
return console;
}
/// <summary> /// <summary>
/// Makes the console interactive. /// Makes the console interactive.
/// </summary> /// </summary>

View File

@ -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();
}
}
}
}

View File

@ -6,6 +6,8 @@ using Xunit;
namespace Spectre.Console.Tests.Unit namespace Spectre.Console.Tests.Unit
{ {
public partial class AnsiConsoleTests public partial class AnsiConsoleTests
{
public sealed class Clear
{ {
[Theory] [Theory]
[InlineData(false, "HelloWorld")] [InlineData(false, "HelloWorld")]
@ -25,7 +27,10 @@ namespace Spectre.Console.Tests.Unit
// Then // Then
console.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
}
public sealed class Write
{
[Fact] [Fact]
public void Should_Combine_Decoration_And_Colors() public void Should_Combine_Decoration_And_Colors()
{ {
@ -105,6 +110,7 @@ namespace Spectre.Console.Tests.Unit
// Then // Then
console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m");
} }
}
public sealed class WriteLine public sealed class WriteLine
{ {

View File

@ -16,7 +16,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not /// Gets or sets a value indicating whether or not
/// the console supports Ansi. /// the console supports VT/ANSI control codes.
/// </summary> /// </summary>
public bool Ansi { get; set; } public bool Ansi { get; set; }

View File

@ -0,0 +1,28 @@
using System;
namespace Spectre.Console.Advanced
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static class AnsiConsoleExtensions
{
/// <summary>
/// Writes a VT/Ansi control code sequence to the console (if supported).
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="sequence">The VT/Ansi control code sequence to write.</param>
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));
}
}
}
}

View File

@ -38,6 +38,11 @@ namespace Spectre.Console
/// <param name="text">The text to write.</param> /// <param name="text">The text to write.</param>
public static void Write(this IAnsiConsole console, string text) 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)); console.Write(new Text(text, Style.Plain));
} }
@ -49,6 +54,11 @@ namespace Spectre.Console
/// <param name="style">The text style.</param> /// <param name="style">The text style.</param>
public static void Write(this IAnsiConsole console, string text, Style 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)); console.Write(new Text(text, style));
} }

View File

@ -14,7 +14,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets a value indicating whether or not /// Gets a value indicating whether or not
/// the console supports Ansi. /// the console supports VT/ANSI control codes.
/// </summary> /// </summary>
bool Ansi { get; } bool Ansi { get; }

View File

@ -22,12 +22,12 @@ namespace Spectre.Console
public void Clear(bool home) public void Clear(bool home)
{ {
Write(new ControlSequence(ED(2))); Write(new ControlCode(ED(2)));
Write(new ControlSequence(ED(3))); Write(new ControlCode(ED(3)));
if (home) if (home)
{ {
Write(new ControlSequence(CUP(1, 1))); Write(new ControlCode(CUP(1, 1)));
} }
} }

View File

@ -16,11 +16,11 @@ namespace Spectre.Console
{ {
if (show) if (show)
{ {
_backend.Write(new ControlSequence(SM(DECTCEM))); _backend.Write(new ControlCode(SM(DECTCEM)));
} }
else else
{ {
_backend.Write(new ControlSequence(RM(DECTCEM))); _backend.Write(new ControlCode(RM(DECTCEM)));
} }
} }
@ -34,23 +34,23 @@ namespace Spectre.Console
switch (direction) switch (direction)
{ {
case CursorDirection.Up: case CursorDirection.Up:
_backend.Write(new ControlSequence(CUU(steps))); _backend.Write(new ControlCode(CUU(steps)));
break; break;
case CursorDirection.Down: case CursorDirection.Down:
_backend.Write(new ControlSequence(CUD(steps))); _backend.Write(new ControlCode(CUD(steps)));
break; break;
case CursorDirection.Right: case CursorDirection.Right:
_backend.Write(new ControlSequence(CUF(steps))); _backend.Write(new ControlCode(CUF(steps)));
break; break;
case CursorDirection.Left: case CursorDirection.Left:
_backend.Write(new ControlSequence(CUB(steps))); _backend.Write(new ControlCode(CUB(steps)));
break; break;
} }
} }
public void SetPosition(int column, int line) public void SetPosition(int column, int line)
{ {
_backend.Write(new ControlSequence(CUP(line, column))); _backend.Write(new ControlCode(CUP(line, column)));
} }
} }
} }

View File

@ -25,11 +25,11 @@ namespace Spectre.Console.Rendering
{ {
if (_shape == null) if (_shape == null)
{ {
return new ControlSequence(string.Empty); return new ControlCode(string.Empty);
} }
var linesToMoveUp = _shape.Value.Height - 1; 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) if (_shape == null)
{ {
return new ControlSequence(string.Empty); return new ControlCode(string.Empty);
} }
var linesToClear = _shape.Value.Height - 1; 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));
} }
} }

View File

@ -14,6 +14,11 @@ namespace Spectre.Console.Rendering
/// </summary> /// </summary>
public ColorSystem ColorSystem => _capabilities.ColorSystem; public ColorSystem ColorSystem => _capabilities.ColorSystem;
/// <summary>
/// Gets a value indicating whether or not VT/Ansi codes are supported.
/// </summary>
public bool Ansi => _capabilities.Ansi;
/// <summary> /// <summary>
/// Gets a value indicating whether or not unicode is supported. /// Gets a value indicating whether or not unicode is supported.
/// </summary> /// </summary>

View File

@ -3,11 +3,11 @@ using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
internal sealed class ControlSequence : Renderable internal sealed class ControlCode : Renderable
{ {
private readonly Segment _segment; private readonly Segment _segment;
public ControlSequence(string control) public ControlCode(string control)
{ {
_segment = Segment.Control(control); _segment = Segment.Control(control);
} }
@ -18,8 +18,11 @@ namespace Spectre.Console
} }
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
if (context.Ansi)
{ {
yield return _segment; yield return _segment;
} }
} }
}
} }

View File

@ -73,7 +73,7 @@ namespace Spectre.Console
public void Refresh() public void Refresh()
{ {
_renderer.Update(this); _renderer.Update(this);
_console.Write(new ControlSequence(string.Empty)); _console.Write(new ControlCode(string.Empty));
} }
internal IReadOnlyList<ProgressTask> GetTasks() internal IReadOnlyList<ProgressTask> GetTasks()

View File

@ -38,7 +38,7 @@ namespace Spectre.Console
public void Redraw() public void Redraw()
{ {
_console.Write(new ControlSequence(string.Empty)); _console.Write(new ControlCode(string.Empty));
} }
public bool Update(ConsoleKey key) public bool Update(ConsoleKey key)