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:
Patrik Svensson
2021-03-24 23:09:24 +01:00
committed by Phil Scott
parent 2ba6da3514
commit 20650f1e7e
75 changed files with 492 additions and 553 deletions

View File

@ -9,10 +9,11 @@ namespace Spectre.Console.Rendering
public interface IAnsiConsoleEncoder
{
/// <summary>
/// Encodes the specified segments.
/// Encodes the specified <see cref="IRenderable"/> enumerator.
/// </summary>
/// <param name="segments">The segments to encode.</param>
/// <returns>The encoded string.</returns>
string Encode(IEnumerable<Segment> segments);
/// <param name="console">The console to use when encoding.</param>
/// <param name="renderable">The renderable objects to encode.</param>
/// <returns>A string representing the encoded result.</returns>
string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderable);
}
}

View File

@ -51,7 +51,7 @@ namespace Spectre.Console.Rendering
if (_renderable != null)
{
var segments = _renderable.Render(context, maxWidth);
var lines = Segment.SplitLines(context, segments);
var lines = Segment.SplitLines(segments);
var shape = SegmentShape.Calculate(context, lines);
_shape = _shape == null ? shape : _shape.Value.Inflate(shape);

View File

@ -1,4 +1,4 @@
using System.Text;
using System;
namespace Spectre.Console.Rendering
{
@ -7,20 +7,12 @@ namespace Spectre.Console.Rendering
/// </summary>
public sealed class RenderContext
{
/// <summary>
/// Gets the console's output encoding.
/// </summary>
public Encoding Encoding { get; }
/// <summary>
/// Gets a value indicating whether or not this a legacy console (i.e. cmd.exe).
/// </summary>
public bool LegacyConsole { get; }
private readonly IReadOnlyCapabilities _capabilities;
/// <summary>
/// Gets a value indicating whether or not unicode is supported.
/// </summary>
public bool Unicode { get; }
public bool Unicode => _capabilities.Unicode;
/// <summary>
/// Gets the current justification.
@ -36,20 +28,18 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Initializes a new instance of the <see cref="RenderContext"/> class.
/// </summary>
/// <param name="encoding">The console's output encoding.</param>
/// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param>
/// <param name="justification">The justification to use when rendering.</param>
public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null)
: this(encoding, legacyConsole, justification, false)
/// <param name="capabilities">The capabilities.</param>
/// <param name="justification">The justification.</param>
public RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null)
: this(capabilities, justification, false)
{
}
private RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null, bool singleLine = false)
private RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null, bool singleLine = false)
{
Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
LegacyConsole = legacyConsole;
_capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
Justification = justification;
Unicode = Encoding.EncodingName.ContainsExact("Unicode");
SingleLine = singleLine;
}
@ -60,7 +50,7 @@ namespace Spectre.Console.Rendering
/// <returns>A new <see cref="RenderContext"/> instance.</returns>
public RenderContext WithJustification(Justify? justification)
{
return new RenderContext(Encoding, LegacyConsole, justification);
return new RenderContext(_capabilities, justification, SingleLine);
}
/// <summary>
@ -75,7 +65,7 @@ namespace Spectre.Console.Rendering
/// <returns>A new <see cref="RenderContext"/> instance.</returns>
internal RenderContext WithSingleLine()
{
return new RenderContext(Encoding, LegacyConsole, Justification, true);
return new RenderContext(_capabilities, Justification, true);
}
}
}

View File

@ -100,36 +100,24 @@ namespace Spectre.Console.Rendering
/// Gets the number of cells that this segment
/// occupies in the console.
/// </summary>
/// <param name="context">The render context.</param>
/// <returns>The number of cells that this segment occupies in the console.</returns>
public int CellCount(RenderContext context)
public int CellCount()
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (IsControlCode)
{
return 0;
}
return Text.CellLength(context);
return Cell.GetCellLength(Text);
}
/// <summary>
/// Gets the number of cells that the segments occupies in the console.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="segments">The segments to measure.</param>
/// <returns>The number of cells that the segments occupies in the console.</returns>
public static int CellCount(RenderContext context, IEnumerable<Segment> segments)
public static int CellCount(IEnumerable<Segment> segments)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segments is null)
{
throw new ArgumentNullException(nameof(segments));
@ -138,7 +126,7 @@ namespace Spectre.Console.Rendering
var sum = 0;
foreach (var segment in segments)
{
sum += segment.CellCount(context);
sum += segment.CellCount();
}
return sum;
@ -158,7 +146,6 @@ namespace Spectre.Console.Rendering
/// </summary>
/// <param name="offset">The offset where to split the segment.</param>
/// <returns>One or two new segments representing the split.</returns>
[Obsolete("Use Split(RenderContext, Int32) instead")]
public (Segment First, Segment? Second) Split(int offset)
{
if (offset < 0)
@ -166,30 +153,7 @@ namespace Spectre.Console.Rendering
return (this, null);
}
if (offset >= Text.Length)
{
return (this, null);
}
return (
new Segment(Text.Substring(0, offset), Style),
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
/// <summary>
/// Splits the segment at the offset.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="offset">The offset where to split the segment.</param>
/// <returns>One or two new segments representing the split.</returns>
public (Segment First, Segment? Second) Split(RenderContext context, int offset)
{
if (offset < 0)
{
return (this, null);
}
if (offset >= CellCount(context))
if (offset >= CellCount())
{
return (this, null);
}
@ -201,7 +165,7 @@ namespace Spectre.Console.Rendering
foreach (var character in Text)
{
index++;
accumulated += Cell.GetCellLength(context, character);
accumulated += Cell.GetCellLength(character);
if (accumulated >= offset)
{
break;
@ -226,38 +190,26 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Splits the provided segments into lines.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="segments">The segments to split.</param>
/// <returns>A collection of lines.</returns>
public static List<SegmentLine> SplitLines(RenderContext context, IEnumerable<Segment> segments)
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segments is null)
{
throw new ArgumentNullException(nameof(segments));
}
return SplitLines(context, segments, int.MaxValue);
return SplitLines(segments, int.MaxValue);
}
/// <summary>
/// Splits the provided segments into lines with a maximum width.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="segments">The segments to split into lines.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <returns>A list of lines.</returns>
public static List<SegmentLine> SplitLines(RenderContext context, IEnumerable<Segment> segments, int maxWidth)
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segments is null)
{
throw new ArgumentNullException(nameof(segments));
@ -271,16 +223,16 @@ namespace Spectre.Console.Rendering
while (stack.Count > 0)
{
var segment = stack.Pop();
var segmentLength = segment.CellCount(context);
var segmentLength = segment.CellCount();
// Does this segment make the line exceed the max width?
var lineLength = line.CellCount(context);
var lineLength = line.CellCount();
if (lineLength + segmentLength > maxWidth)
{
var diff = -(maxWidth - (lineLength + segmentLength));
var offset = segment.Text.Length - diff;
var (first, second) = segment.Split(context, offset);
var (first, second) = segment.Split(offset);
line.Add(first);
lines.Add(line);
@ -356,22 +308,16 @@ namespace Spectre.Console.Rendering
/// </summary>
/// <param name="segment">The segment to split.</param>
/// <param name="overflow">The overflow strategy to use.</param>
/// <param name="context">The render context.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <returns>A list of segments that has been split.</returns>
public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, RenderContext context, int maxWidth)
public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, int maxWidth)
{
if (segment is null)
{
throw new ArgumentNullException(nameof(segment));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segment.CellCount(context) <= maxWidth)
if (segment.CellCount() <= maxWidth)
{
return new List<Segment>(1) { segment };
}
@ -383,7 +329,7 @@ namespace Spectre.Console.Rendering
if (overflow == Overflow.Fold)
{
var totalLength = segment.Text.CellLength(context);
var totalLength = segment.Text.CellLength();
var lengthLeft = totalLength;
while (lengthLeft > 0)
{
@ -431,17 +377,11 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Truncates the segments to the specified width.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="segments">The segments to truncate.</param>
/// <param name="maxWidth">The maximum width that the segments may occupy.</param>
/// <returns>A list of segments that has been truncated.</returns>
public static List<Segment> Truncate(RenderContext context, IEnumerable<Segment> segments, int maxWidth)
public static List<Segment> Truncate(IEnumerable<Segment> segments, int maxWidth)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segments is null)
{
throw new ArgumentNullException(nameof(segments));
@ -452,7 +392,7 @@ namespace Spectre.Console.Rendering
var totalWidth = 0;
foreach (var segment in segments)
{
var segmentCellWidth = segment.CellCount(context);
var segmentCellWidth = segment.CellCount();
if (totalWidth + segmentCellWidth > maxWidth)
{
break;
@ -464,7 +404,7 @@ namespace Spectre.Console.Rendering
if (result.Count == 0 && segments.Any())
{
var segment = Truncate(context, segments.First(), maxWidth);
var segment = Truncate(segments.First(), maxWidth);
if (segment != null)
{
result.Add(segment);
@ -477,23 +417,17 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Truncates the segment to the specified width.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="segment">The segment to truncate.</param>
/// <param name="maxWidth">The maximum width that the segment may occupy.</param>
/// <returns>A new truncated segment, or <c>null</c>.</returns>
public static Segment? Truncate(RenderContext context, Segment? segment, int maxWidth)
public static Segment? Truncate(Segment? segment, int maxWidth)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (segment is null)
{
return null;
}
if (segment.CellCount(context) <= maxWidth)
if (segment.CellCount() <= maxWidth)
{
return segment;
}
@ -501,7 +435,7 @@ namespace Spectre.Console.Rendering
var builder = new StringBuilder();
foreach (var character in segment.Text)
{
var accumulatedCellWidth = builder.ToString().CellLength(context);
var accumulatedCellWidth = builder.ToString().CellLength();
if (accumulatedCellWidth >= maxWidth)
{
break;
@ -562,24 +496,19 @@ namespace Spectre.Console.Rendering
return result;
}
internal static List<Segment> TruncateWithEllipsis(IEnumerable<Segment> segments, RenderContext context, int maxWidth)
internal static List<Segment> TruncateWithEllipsis(IEnumerable<Segment> segments, int maxWidth)
{
if (segments is null)
{
throw new ArgumentNullException(nameof(segments));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (CellCount(context, segments) <= maxWidth)
if (CellCount(segments) <= maxWidth)
{
return new List<Segment>(segments);
}
segments = TrimEnd(Truncate(context, segments, maxWidth - 1));
segments = TrimEnd(Truncate(segments, maxWidth - 1));
if (!segments.Any())
{
return new List<Segment>(1);

View File

@ -16,16 +16,10 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Gets the number of cells the segment line occupies.
/// </summary>
/// <param name="context">The render context.</param>
/// <returns>The cell width of the segment line.</returns>
public int CellCount(RenderContext context)
public int CellCount()
{
if (context is null)
{
throw new System.ArgumentNullException(nameof(context));
}
return Segment.CellCount(context, this);
return Segment.CellCount(this);
}
/// <summary>

View File

@ -28,7 +28,7 @@ namespace Spectre.Console.Rendering
}
var height = lines.Count;
var width = lines.Max(l => Segment.CellCount(context, l));
var width = lines.Max(l => Segment.CellCount(l));
return new SegmentShape(width, height);
}
@ -44,7 +44,7 @@ namespace Spectre.Console.Rendering
{
foreach (var line in lines)
{
var length = Segment.CellCount(context, line);
var length = Segment.CellCount(line);
var missing = Width - length;
if (missing > 0)
{