mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00
Change IAnsiConsole to render IRenderable
This makes it possible for encoders to output better representation of the actual objects instead of working with chopped up segments. * IAnsiConsole.Write now takes an IRenderable instead of segments * Calculating cell width does no longer require a render context * Removed RenderContext.LegacyConsole * Removed RenderContext.Encoding * Added Capabilities.Unicode
This commit is contained in:

committed by
Phil Scott

parent
2ba6da3514
commit
20650f1e7e
@ -6,14 +6,14 @@ namespace Spectre.Console
|
||||
{
|
||||
internal static class Aligner
|
||||
{
|
||||
public static string Align(RenderContext context, string text, Justify? alignment, int maxWidth)
|
||||
public static string Align(string text, Justify? alignment, int maxWidth)
|
||||
{
|
||||
if (alignment == null || alignment == Justify.Left)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
var width = Cell.GetCellLength(context, text);
|
||||
var width = Cell.GetCellLength(text);
|
||||
if (width >= maxWidth)
|
||||
{
|
||||
return text;
|
||||
@ -57,7 +57,7 @@ namespace Spectre.Console
|
||||
return;
|
||||
}
|
||||
|
||||
var width = Segment.CellCount(context, segments);
|
||||
var width = Segment.CellCount(segments);
|
||||
if (width >= maxWidth)
|
||||
{
|
||||
return;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
@ -8,21 +7,21 @@ namespace Spectre.Console
|
||||
internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend
|
||||
{
|
||||
private readonly AnsiBuilder _builder;
|
||||
private readonly Profile _profile;
|
||||
private readonly IAnsiConsole _console;
|
||||
|
||||
public IAnsiConsoleCursor Cursor { get; }
|
||||
|
||||
public AnsiConsoleBackend(Profile profile)
|
||||
public AnsiConsoleBackend(IAnsiConsole console)
|
||||
{
|
||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
_builder = new AnsiBuilder(profile);
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_builder = new AnsiBuilder(_console.Profile);
|
||||
|
||||
Cursor = new AnsiConsoleCursor(this);
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
Render(new[] { Segment.Control("\u001b[2J") });
|
||||
Write(new ControlSequence("\u001b[2J"));
|
||||
|
||||
if (home)
|
||||
{
|
||||
@ -30,10 +29,10 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IEnumerable<Segment> segments)
|
||||
public void Write(IRenderable renderable)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var segment in segments)
|
||||
foreach (var segment in renderable.GetSegments(_console))
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
@ -58,8 +57,8 @@ namespace Spectre.Console
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
_profile.Out.Write(builder.ToString());
|
||||
_profile.Out.Flush();
|
||||
_console.Profile.Out.Write(builder.ToString());
|
||||
_console.Profile.Out.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -16,11 +15,11 @@ namespace Spectre.Console
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control("\u001b[?25h") });
|
||||
_backend.Write(new ControlSequence("\u001b[?25h"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control("\u001b[?25l") });
|
||||
_backend.Write(new ControlSequence("\u001b[?25l"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,23 +33,23 @@ namespace Spectre.Console
|
||||
switch (direction)
|
||||
{
|
||||
case CursorDirection.Up:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}A") });
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}A"));
|
||||
break;
|
||||
case CursorDirection.Down:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}B") });
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}B"));
|
||||
break;
|
||||
case CursorDirection.Right:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}C") });
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}C"));
|
||||
break;
|
||||
case CursorDirection.Left:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}D") });
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}D"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(int column, int line)
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{line};{column}H") });
|
||||
_backend.Write(new ControlSequence($"\u001b[{line};{column}H"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
@ -19,13 +18,14 @@ namespace Spectre.Console
|
||||
public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode)
|
||||
{
|
||||
_renderLock = new object();
|
||||
_ansiBackend = new AnsiConsoleBackend(profile);
|
||||
_legacyBackend = new LegacyConsoleBackend(profile);
|
||||
|
||||
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
Input = new DefaultInput(Profile);
|
||||
ExclusivityMode = exclusivityMode ?? throw new ArgumentNullException(nameof(exclusivityMode));
|
||||
Pipeline = new RenderPipeline();
|
||||
|
||||
_ansiBackend = new AnsiConsoleBackend(this);
|
||||
_legacyBackend = new LegacyConsoleBackend(this);
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
@ -36,11 +36,11 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(IEnumerable<Segment> segments)
|
||||
public void Write(IRenderable renderable)
|
||||
{
|
||||
lock (_renderLock)
|
||||
{
|
||||
GetBackend().Render(segments);
|
||||
GetBackend().Write(renderable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
@ -20,9 +19,9 @@ namespace Spectre.Console
|
||||
void Clear(bool home);
|
||||
|
||||
/// <summary>
|
||||
/// Renders segments to the console.
|
||||
/// Writes a <see cref="IRenderable"/> to the console backend.
|
||||
/// </summary>
|
||||
/// <param name="segments">The segments to render.</param>
|
||||
void Render(IEnumerable<Segment> segments);
|
||||
/// <param name="renderable">The <see cref="IRenderable"/> to write.</param>
|
||||
void Write(IRenderable renderable);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class LegacyConsoleBackend : IAnsiConsoleBackend
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private readonly IAnsiConsole _console;
|
||||
private Style _lastStyle;
|
||||
|
||||
public IAnsiConsoleCursor Cursor { get; }
|
||||
|
||||
public LegacyConsoleBackend(Profile profile)
|
||||
public LegacyConsoleBackend(IAnsiConsole console)
|
||||
{
|
||||
_profile = profile ?? throw new System.ArgumentNullException(nameof(profile));
|
||||
_console = console ?? throw new System.ArgumentNullException(nameof(console));
|
||||
_lastStyle = Style.Plain;
|
||||
|
||||
Cursor = new LegacyConsoleCursor();
|
||||
@ -31,9 +30,9 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IEnumerable<Segment> segments)
|
||||
public void Write(IRenderable renderable)
|
||||
{
|
||||
foreach (var segment in segments)
|
||||
foreach (var segment in renderable.GetSegments(_console))
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
@ -45,7 +44,7 @@ namespace Spectre.Console
|
||||
SetStyle(segment.Style);
|
||||
}
|
||||
|
||||
_profile.Out.Write(segment.Text.NormalizeNewLines(native: true));
|
||||
_console.Profile.Out.Write(segment.Text.NormalizeNewLines(native: true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,13 +55,13 @@ namespace Spectre.Console
|
||||
System.Console.ResetColor();
|
||||
|
||||
var background = Color.ToConsoleColor(style.Background);
|
||||
if (_profile.ColorSystem != ColorSystem.NoColors && (int)background != -1)
|
||||
if (_console.Profile.ColorSystem != ColorSystem.NoColors && (int)background != -1)
|
||||
{
|
||||
System.Console.BackgroundColor = background;
|
||||
}
|
||||
|
||||
var foreground = Color.ToConsoleColor(style.Foreground);
|
||||
if (_profile.ColorSystem != ColorSystem.NoColors && (int)foreground != -1)
|
||||
if (_console.Profile.ColorSystem != ColorSystem.NoColors && (int)foreground != -1)
|
||||
{
|
||||
System.Console.ForegroundColor = foreground;
|
||||
}
|
||||
|
@ -1,34 +1,22 @@
|
||||
using Spectre.Console.Rendering;
|
||||
using Wcwidth;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class Cell
|
||||
{
|
||||
public static int GetCellLength(RenderContext context, string text)
|
||||
public static int GetCellLength(string text)
|
||||
{
|
||||
var sum = 0;
|
||||
foreach (var rune in text)
|
||||
{
|
||||
sum += GetCellLength(context, rune);
|
||||
sum += GetCellLength(rune);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static int GetCellLength(RenderContext context, char rune)
|
||||
public static int GetCellLength(char rune)
|
||||
{
|
||||
if (context.LegacyConsole)
|
||||
{
|
||||
// Is it represented by a single byte?
|
||||
// In that case we don't have to calculate the
|
||||
// actual cell width.
|
||||
if (context.Encoding.GetByteCount(new[] { rune }) == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We need to figure out why Segment.SplitLines fails
|
||||
// if we let wcwidth (which returns -1 instead of 1)
|
||||
// calculate the size for new line characters.
|
||||
|
@ -3,52 +3,57 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class HtmlEncoder : IAnsiConsoleEncoder
|
||||
{
|
||||
public string Encode(IEnumerable<Segment> segments)
|
||||
public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
|
||||
{
|
||||
var context = new RenderContext(EncoderCapabilities.Default);
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n");
|
||||
|
||||
foreach (var (_, first, _, segment) in segments.Enumerate())
|
||||
foreach (var renderable in renderables)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
var segments = renderable.Render(context, console.Profile.Width);
|
||||
foreach (var (_, first, _, segment) in segments.Enumerate())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segment.Text == "\n" && !first)
|
||||
{
|
||||
builder.Append('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = segment.Text.Split(new[] { '\n' }, StringSplitOptions.None);
|
||||
foreach (var (_, _, last, line) in parts.Enumerate())
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append("<span");
|
||||
if (!segment.Style.Equals(Style.Plain))
|
||||
{
|
||||
builder.Append(" style=\"");
|
||||
builder.Append(BuildCss(segment.Style));
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
builder.Append('>');
|
||||
builder.Append(line);
|
||||
builder.Append("</span>");
|
||||
|
||||
if (parts.Length > 1 && !last)
|
||||
if (segment.Text == "\n" && !first)
|
||||
{
|
||||
builder.Append('\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = segment.Text.Split(new[] { '\n' }, StringSplitOptions.None);
|
||||
foreach (var (_, _, last, line) in parts.Enumerate())
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append("<span");
|
||||
if (!segment.Style.Equals(Style.Plain))
|
||||
{
|
||||
builder.Append(" style=\"");
|
||||
builder.Append(BuildCss(segment.Style));
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
builder.Append('>');
|
||||
builder.Append(line);
|
||||
builder.Append("</span>");
|
||||
|
||||
if (parts.Length > 1 && !last)
|
||||
{
|
||||
builder.Append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,39 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class EncoderCapabilities : IReadOnlyCapabilities
|
||||
{
|
||||
public bool Ansi => false;
|
||||
public bool Links => false;
|
||||
public bool Legacy => false;
|
||||
public bool Tty => false;
|
||||
public bool Interactive => false;
|
||||
public bool Unicode => true;
|
||||
|
||||
public static EncoderCapabilities Default { get; } = new EncoderCapabilities();
|
||||
}
|
||||
|
||||
internal sealed class TextEncoder : IAnsiConsoleEncoder
|
||||
{
|
||||
public string Encode(IEnumerable<Segment> segments)
|
||||
public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables)
|
||||
{
|
||||
var context = new RenderContext(EncoderCapabilities.Default);
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var segment in Segment.Merge(segments))
|
||||
foreach (var renderable in renderables)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
var segments = renderable.Render(context, console.Profile.Width);
|
||||
foreach (var segment in Segment.Merge(segments))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append(segment.Text);
|
||||
builder.Append(segment.Text);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd('\n');
|
||||
|
Reference in New Issue
Block a user