Add method to get VT/ANSI codes for renderables

This commit is contained in:
Patrik Svensson 2021-04-23 00:36:43 +02:00 committed by Phil Scott
parent 01f707c78d
commit cba02070f9
4 changed files with 77 additions and 40 deletions

View File

@ -5,7 +5,7 @@ using Xunit;
namespace Spectre.Console.Tests.Unit namespace Spectre.Console.Tests.Unit
{ {
public partial class AnsiConsoleTests public sealed partial class AnsiConsoleTests
{ {
public sealed class Advanced public sealed class Advanced
{ {
@ -42,6 +42,20 @@ namespace Spectre.Console.Tests.Unit
console.Output.NormalizeLineEndings() console.Output.NormalizeLineEndings()
.ShouldBeEmpty(); .ShouldBeEmpty();
} }
[Fact]
public void Should_Return_Ansi_For_Renderable()
{
// Given
var console = new TestConsole().Colors(ColorSystem.TrueColor);
var markup = new Console.Markup("[yellow]Hello [blue]World[/]![/]");
// When
var result = console.ToAnsi(markup);
// Then
result.ShouldBe("Hello World!");
}
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using Spectre.Console.Rendering;
namespace Spectre.Console.Advanced namespace Spectre.Console.Advanced
{ {
@ -24,5 +25,16 @@ namespace Spectre.Console.Advanced
console.Write(new ControlCode(sequence)); console.Write(new ControlCode(sequence));
} }
} }
/// <summary>
/// Gets the VT/ANSI control code sequence for a <see cref="IRenderable"/>.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="renderable">The renderable to the VT/ANSI control code sequence for.</param>
/// <returns>The VT/ANSI control code sequence.</returns>
public static string ToAnsi(this IAnsiConsole console, IRenderable renderable)
{
return AnsiBuilder.Build(console, renderable);
}
} }
} }

View File

@ -1,23 +1,60 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text;
using Spectre.Console.Rendering;
using static Spectre.Console.AnsiSequences; using static Spectre.Console.AnsiSequences;
namespace Spectre.Console namespace Spectre.Console
{ {
internal sealed class AnsiBuilder internal static class AnsiBuilder
{ {
private readonly Profile _profile; private static readonly AnsiLinkHasher _linkHasher;
private readonly AnsiLinkHasher _linkHasher;
public AnsiBuilder(Profile profile) static AnsiBuilder()
{ {
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
_linkHasher = new AnsiLinkHasher(); _linkHasher = new AnsiLinkHasher();
} }
public string GetAnsi(string text, Style style) public static string Build(IAnsiConsole console, IRenderable renderable)
{ {
if (style is null) var builder = new StringBuilder();
foreach (var segment in renderable.GetSegments(console))
{
if (segment.IsControlCode)
{
builder.Append(segment.Text);
continue;
}
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate())
{
if (!string.IsNullOrEmpty(part))
{
builder.Append(Build(console.Profile, part, segment.Style));
}
if (!last)
{
builder.Append(Environment.NewLine);
}
}
}
return builder.ToString();
}
private static string Build(Profile profile, string text, Style style)
{
if (profile is null)
{
throw new ArgumentNullException(nameof(profile));
}
else if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
else if (style is null)
{ {
throw new ArgumentNullException(nameof(style)); throw new ArgumentNullException(nameof(style));
} }
@ -29,7 +66,7 @@ namespace Spectre.Console
{ {
codes = codes.Concat( codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes( AnsiColorBuilder.GetAnsiCodes(
_profile.Capabilities.ColorSystem, profile.Capabilities.ColorSystem,
style.Foreground, style.Foreground,
true)); true));
} }
@ -39,7 +76,7 @@ namespace Spectre.Console
{ {
codes = codes.Concat( codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes( AnsiColorBuilder.GetAnsiCodes(
_profile.Capabilities.ColorSystem, profile.Capabilities.ColorSystem,
style.Background, style.Background,
false)); false));
} }
@ -54,7 +91,7 @@ namespace Spectre.Console
? $"{SGR(result)}{text}{SGR(0)}" ? $"{SGR(result)}{text}{SGR(0)}"
: text; : text;
if (style.Link != null && !_profile.Capabilities.Legacy) if (style.Link != null && !profile.Capabilities.Legacy)
{ {
var link = style.Link; var link = style.Link;

View File

@ -7,7 +7,6 @@ namespace Spectre.Console
{ {
internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend
{ {
private readonly AnsiBuilder _builder;
private readonly IAnsiConsole _console; private readonly IAnsiConsole _console;
public IAnsiConsoleCursor Cursor { get; } public IAnsiConsoleCursor Cursor { get; }
@ -15,8 +14,6 @@ namespace Spectre.Console
public AnsiConsoleBackend(IAnsiConsole console) public AnsiConsoleBackend(IAnsiConsole console)
{ {
_console = console ?? throw new ArgumentNullException(nameof(console)); _console = console ?? throw new ArgumentNullException(nameof(console));
_builder = new AnsiBuilder(_console.Profile);
Cursor = new AnsiConsoleCursor(this); Cursor = new AnsiConsoleCursor(this);
} }
@ -33,33 +30,10 @@ namespace Spectre.Console
public void Write(IRenderable renderable) public void Write(IRenderable renderable)
{ {
var builder = new StringBuilder(); var result = AnsiBuilder.Build(_console, renderable);
foreach (var segment in renderable.GetSegments(_console)) if (result?.Length > 0)
{ {
if (segment.IsControlCode) _console.Profile.Out.Writer.Write(result);
{
builder.Append(segment.Text);
continue;
}
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate())
{
if (!string.IsNullOrEmpty(part))
{
builder.Append(_builder.GetAnsi(part, segment.Style));
}
if (!last)
{
builder.Append(Environment.NewLine);
}
}
}
if (builder.Length > 0)
{
_console.Profile.Out.Writer.Write(builder.ToString());
_console.Profile.Out.Writer.Flush(); _console.Profile.Out.Writer.Flush();
} }
} }