Add profile support

Closes #231
This commit is contained in:
Patrik Svensson
2021-01-16 17:23:58 +01:00
committed by Patrik Svensson
parent 913a7b1e37
commit a23bec4082
230 changed files with 1241 additions and 1628 deletions

View File

@ -1,41 +0,0 @@
using System.Linq;
using Spectre.Console.Rendering;
using Wcwidth;
namespace Spectre.Console.Internal
{
internal static class Cell
{
public static int GetCellLength(RenderContext context, string text)
{
return text.Sum(rune => GetCellLength(context, rune));
}
public static int GetCellLength(RenderContext context, 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.
// That is correct from a Unicode perspective, but the
// algorithm was written before wcwidth was added and used
// to work with string length and not cell length.
if (rune == '\n')
{
return 1;
}
return UnicodeCalculator.GetWidth(rune);
}
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
internal sealed class HtmlEncoder : IAnsiConsoleEncoder
{
public string Encode(IEnumerable<Segment> segments)
{
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())
{
if (segment.IsControlCode)
{
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))
{
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');
}
}
}
builder.Append("</pre>");
return builder.ToString().TrimEnd('\n');
}
private static string BuildCss(Style style)
{
var css = new List<string>();
var foreground = style.Foreground;
var background = style.Background;
if ((style.Decoration & Decoration.Invert) != 0)
{
var temp = foreground;
foreground = background;
background = temp;
}
if ((style.Decoration & Decoration.Dim) != 0)
{
var blender = background;
if (blender.Equals(Color.Default))
{
blender = Color.White;
}
foreground = foreground.Blend(blender, 0.5f);
}
if (!foreground.Equals(Color.Default))
{
css.Add($"color: #{foreground.ToHex()}");
}
if (!background.Equals(Color.Default))
{
css.Add($"background-color: #{background.ToHex()}");
}
if ((style.Decoration & Decoration.Bold) != 0)
{
css.Add("font-weight: bold");
}
if ((style.Decoration & Decoration.Bold) != 0)
{
css.Add("font-style: italic");
}
if ((style.Decoration & Decoration.Underline) != 0)
{
css.Add("text-decoration: underline");
}
if ((style.Decoration & Decoration.Strikethrough) != 0)
{
css.Add("text-decoration: line-through");
}
return string.Join(";", css);
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
internal sealed class TextEncoder : IAnsiConsoleEncoder
{
public string Encode(IEnumerable<Segment> segments)
{
var builder = new StringBuilder();
foreach (var segment in Segment.Merge(segments))
{
if (segment.IsControlCode)
{
continue;
}
builder.Append(segment.Text);
}
return builder.ToString().TrimEnd('\n');
}
}
}

View File

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Internal
namespace Spectre.Console
{
internal static class MarkupParser
{

View File

@ -1,6 +1,6 @@
using System;
namespace Spectre.Console.Internal
namespace Spectre.Console
{
internal sealed class MarkupToken
{

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Internal
namespace Spectre.Console
{
internal enum MarkupTokenKind
{

View File

@ -1,7 +1,7 @@
using System;
using System.Text;
namespace Spectre.Console.Internal
namespace Spectre.Console
{
internal sealed class MarkupTokenizer : IDisposable
{

View File

@ -1,12 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace Spectre.Console.Internal
namespace Spectre.Console
{
internal sealed class StringBuffer : IDisposable
{
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")]
private readonly StringReader _reader;
private readonly int _length;

View File

@ -1,218 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Spectre.Console.Internal
{
internal static class StyleParser
{
public static Style Parse(string text)
{
var style = Parse(text, out var error);
if (error != null)
{
throw new InvalidOperationException(error);
}
if (style == null)
{
// This should not happen, but we need to please the compiler
// which cannot know that style isn't null here.
throw new InvalidOperationException("Could not parse style.");
}
return style;
}
public static bool TryParse(string text, out Style? style)
{
style = Parse(text, out var error);
return error == null;
}
private static Style? Parse(string text, out string? error)
{
var effectiveDecoration = (Decoration?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null;
var effectiveLink = (string?)null;
var parts = text.Split(new[] { ' ' });
var foreground = true;
foreach (var part in parts)
{
if (part.Equals("default", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
{
foreground = false;
continue;
}
if (part.StartsWith("link=", StringComparison.OrdinalIgnoreCase))
{
if (effectiveLink != null)
{
error = "A link has already been set.";
return null;
}
effectiveLink = part.Substring(5);
continue;
}
else if (part.StartsWith("link", StringComparison.OrdinalIgnoreCase))
{
effectiveLink = Constants.EmptyLink;
continue;
}
var decoration = DecorationTable.GetDecoration(part);
if (decoration != null)
{
effectiveDecoration ??= Decoration.None;
effectiveDecoration |= decoration.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
color = ParseHexColor(part, out error);
if (!string.IsNullOrWhiteSpace(error))
{
return null;
}
}
else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
{
color = ParseRgbColor(part, out error);
if (!string.IsNullOrWhiteSpace(error))
{
return null;
}
}
else
{
error = !foreground
? $"Could not find color '{part}'."
: $"Could not find color or style '{part}'.";
return null;
}
}
if (foreground)
{
if (effectiveForeground != null)
{
error = "A foreground color has already been set.";
return null;
}
effectiveForeground = color;
}
else
{
if (effectiveBackground != null)
{
error = "A background color has already been set.";
return null;
}
effectiveBackground = color;
}
}
}
error = null;
return new Style(
effectiveForeground,
effectiveBackground,
effectiveDecoration,
effectiveLink);
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
private static Color? ParseHexColor(string hex, out string? error)
{
error = null;
hex ??= string.Empty;
hex = hex.ReplaceExact("#", string.Empty).Trim();
try
{
if (!string.IsNullOrWhiteSpace(hex))
{
if (hex.Length == 6)
{
return new Color(
(byte)Convert.ToUInt32(hex.Substring(0, 2), 16),
(byte)Convert.ToUInt32(hex.Substring(2, 2), 16),
(byte)Convert.ToUInt32(hex.Substring(4, 2), 16));
}
else if (hex.Length == 3)
{
return new Color(
(byte)Convert.ToUInt32(new string(hex[0], 2), 16),
(byte)Convert.ToUInt32(new string(hex[1], 2), 16),
(byte)Convert.ToUInt32(new string(hex[2], 2), 16));
}
}
}
catch (Exception ex)
{
error = $"Invalid hex color '#{hex}'. {ex.Message}";
return null;
}
error = $"Invalid hex color '#{hex}'.";
return null;
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
private static Color? ParseRgbColor(string rgb, out string? error)
{
try
{
error = null;
var normalized = rgb ?? string.Empty;
if (normalized.Length >= 3)
{
// Trim parentheses
normalized = normalized.Substring(3).Trim();
if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) &&
normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized.Trim('(').Trim(')');
var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
{
return new Color(
(byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture),
(byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture),
(byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture));
}
}
}
}
catch (Exception ex)
{
error = $"Invalid RGB color '{rgb}'. {ex.Message}";
return null;
}
error = $"Invalid RGB color '{rgb}'.";
return null;
}
}
}