From cba02070f9c6fbf159c950cdac99cb35e60dbca3 Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Fri, 23 Apr 2021 00:36:43 +0200 Subject: [PATCH] Add method to get VT/ANSI codes for renderables --- .../Unit/AnsiConsoleTests.Advanced.cs | 16 +++++- .../Advanced/AnsiConsoleExtensions.cs | 12 ++++ .../Internal/Backends/Ansi/AnsiBuilder.cs | 57 +++++++++++++++---- .../Backends/Ansi/AnsiConsoleBackend.cs | 32 +---------- 4 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs index 3d23c64..b086bfb 100644 --- a/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs +++ b/src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs @@ -5,7 +5,7 @@ using Xunit; namespace Spectre.Console.Tests.Unit { - public partial class AnsiConsoleTests + public sealed partial class AnsiConsoleTests { public sealed class Advanced { @@ -42,6 +42,20 @@ namespace Spectre.Console.Tests.Unit console.Output.NormalizeLineEndings() .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!"); + } } } } diff --git a/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs b/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs index cf54473..1962088 100644 --- a/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs +++ b/src/Spectre.Console/Extensions/Advanced/AnsiConsoleExtensions.cs @@ -1,4 +1,5 @@ using System; +using Spectre.Console.Rendering; namespace Spectre.Console.Advanced { @@ -24,5 +25,16 @@ namespace Spectre.Console.Advanced console.Write(new ControlCode(sequence)); } } + + /// + /// Gets the VT/ANSI control code sequence for a . + /// + /// The console. + /// The renderable to the VT/ANSI control code sequence for. + /// The VT/ANSI control code sequence. + public static string ToAnsi(this IAnsiConsole console, IRenderable renderable) + { + return AnsiBuilder.Build(console, renderable); + } } } diff --git a/src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs b/src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs index de64b7b..e30923a 100644 --- a/src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs +++ b/src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs @@ -1,23 +1,60 @@ using System; using System.Linq; +using System.Text; +using Spectre.Console.Rendering; using static Spectre.Console.AnsiSequences; namespace Spectre.Console { - internal sealed class AnsiBuilder + internal static class AnsiBuilder { - private readonly Profile _profile; - private readonly AnsiLinkHasher _linkHasher; + private static readonly AnsiLinkHasher _linkHasher; - public AnsiBuilder(Profile profile) + static AnsiBuilder() { - _profile = profile ?? throw new ArgumentNullException(nameof(profile)); _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)); } @@ -29,7 +66,7 @@ namespace Spectre.Console { codes = codes.Concat( AnsiColorBuilder.GetAnsiCodes( - _profile.Capabilities.ColorSystem, + profile.Capabilities.ColorSystem, style.Foreground, true)); } @@ -39,7 +76,7 @@ namespace Spectre.Console { codes = codes.Concat( AnsiColorBuilder.GetAnsiCodes( - _profile.Capabilities.ColorSystem, + profile.Capabilities.ColorSystem, style.Background, false)); } @@ -54,7 +91,7 @@ namespace Spectre.Console ? $"{SGR(result)}{text}{SGR(0)}" : text; - if (style.Link != null && !_profile.Capabilities.Legacy) + if (style.Link != null && !profile.Capabilities.Legacy) { var link = style.Link; diff --git a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs index e278393..071db9a 100644 --- a/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs +++ b/src/Spectre.Console/Internal/Backends/Ansi/AnsiConsoleBackend.cs @@ -7,7 +7,6 @@ namespace Spectre.Console { internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend { - private readonly AnsiBuilder _builder; private readonly IAnsiConsole _console; public IAnsiConsoleCursor Cursor { get; } @@ -15,8 +14,6 @@ namespace Spectre.Console public AnsiConsoleBackend(IAnsiConsole console) { _console = console ?? throw new ArgumentNullException(nameof(console)); - _builder = new AnsiBuilder(_console.Profile); - Cursor = new AnsiConsoleCursor(this); } @@ -33,33 +30,10 @@ namespace Spectre.Console public void Write(IRenderable renderable) { - var builder = new StringBuilder(); - foreach (var segment in renderable.GetSegments(_console)) + var result = AnsiBuilder.Build(_console, renderable); + if (result?.Length > 0) { - 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(_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.Write(result); _console.Profile.Out.Writer.Flush(); } }