Improve text composite

- A `Text` object should not be able to justify itself.
  All justification needs to be done by a parent.
- Apply colors and styles to part of a `Text` object
- Markup parser should return a `Text` object
This commit is contained in:
Patrik Svensson
2020-07-30 23:26:22 +02:00
committed by Patrik Svensson
parent 8e4f33bba4
commit f19202b427
33 changed files with 728 additions and 434 deletions

View File

@ -1,30 +0,0 @@
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal sealed class MarkupBlockNode : IMarkupNode
{
private readonly List<IMarkupNode> _elements;
public MarkupBlockNode()
{
_elements = new List<IMarkupNode>();
}
public void Append(IMarkupNode element)
{
if (element != null)
{
_elements.Add(element);
}
}
public void Render(IAnsiConsole renderer)
{
foreach (var element in _elements)
{
element.Render(renderer);
}
}
}
}

View File

@ -1,52 +0,0 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class MarkupStyleNode : IMarkupNode
{
private readonly Styles? _style;
private readonly Color? _foreground;
private readonly Color? _background;
private readonly IMarkupNode _element;
public MarkupStyleNode(
Styles? style,
Color? foreground,
Color? background,
IMarkupNode element)
{
_style = style;
_foreground = foreground;
_background = background;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
public void Render(IAnsiConsole renderer)
{
var style = (IDisposable)null;
var foreground = (IDisposable)null;
var background = (IDisposable)null;
if (_style != null)
{
style = renderer.PushStyle(_style.Value);
}
if (_foreground != null)
{
foreground = renderer.PushColor(_foreground.Value, foreground: true);
}
if (_background != null)
{
background = renderer.PushColor(_background.Value, foreground: false);
}
_element.Render(renderer);
background?.Dispose();
foreground?.Dispose();
style?.Dispose();
}
}
}

View File

@ -1,19 +0,0 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class MarkupTextNode : IMarkupNode
{
public string Text { get; }
public MarkupTextNode(string text)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
}
public void Render(IAnsiConsole renderer)
{
renderer.Write(Text);
}
}
}

View File

@ -1,14 +0,0 @@
namespace Spectre.Console.Internal
{
/// <summary>
/// Represents a parsed markup node.
/// </summary>
internal interface IMarkupNode
{
/// <summary>
/// Renders the node using the specified renderer.
/// </summary>
/// <param name="renderer">The renderer to use.</param>
void Render(IAnsiConsole renderer);
}
}

View File

@ -5,37 +5,23 @@ namespace Spectre.Console.Internal
{
internal static class MarkupParser
{
public static IMarkupNode Parse(string text)
public static Text Parse(string text, Appearance appearance = null)
{
appearance ??= Appearance.Plain;
var result = new Text(string.Empty);
using var tokenizer = new MarkupTokenizer(text);
var root = new MarkupBlockNode();
var stack = new Stack<MarkupBlockNode>();
var current = root;
var stack = new Stack<Appearance>();
while (true)
while (tokenizer.MoveNext())
{
var token = tokenizer.GetNext();
if (token == null)
{
break;
}
var token = tokenizer.Current;
if (token.Kind == MarkupTokenKind.Text)
{
current.Append(new MarkupTextNode(token.Value));
continue;
}
else if (token.Kind == MarkupTokenKind.Open)
if (token.Kind == MarkupTokenKind.Open)
{
var (style, foreground, background) = MarkupStyleParser.Parse(token.Value);
var content = new MarkupBlockNode();
current.Append(new MarkupStyleNode(style, foreground, background, content));
current = content;
stack.Push(current);
continue;
stack.Push(new Appearance(foreground, background, style));
}
else if (token.Kind == MarkupTokenKind.Close)
{
@ -45,20 +31,17 @@ namespace Spectre.Console.Internal
}
stack.Pop();
if (stack.Count == 0)
{
current = root;
}
else
{
current = stack.Peek();
}
continue;
}
throw new InvalidOperationException("Encountered unkown markup token.");
else if (token.Kind == MarkupTokenKind.Text)
{
// Get the effecive style.
var style = appearance.Combine(stack);
result.Append(token.Value, style);
}
else
{
throw new InvalidOperationException("Encountered unkown markup token.");
}
}
if (stack.Count > 0)
@ -66,7 +49,7 @@ namespace Spectre.Console.Internal
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
}
return root;
return result;
}
}
}

View File

@ -7,6 +7,8 @@ namespace Spectre.Console.Internal
{
private readonly StringBuffer _reader;
public MarkupToken Current { get; private set; }
public MarkupTokenizer(string text)
{
_reader = new StringBuffer(text ?? throw new ArgumentNullException(nameof(text)));
@ -17,11 +19,11 @@ namespace Spectre.Console.Internal
_reader.Dispose();
}
public MarkupToken GetNext()
public bool MoveNext()
{
if (_reader.Eof)
{
return null;
return false;
}
var current = _reader.Peek();
@ -40,7 +42,8 @@ namespace Spectre.Console.Internal
if (current == '[')
{
_reader.Read();
return new MarkupToken(MarkupTokenKind.Text, "[", position);
Current = new MarkupToken(MarkupTokenKind.Text, "[", position);
return true;
}
if (current == '/')
@ -59,7 +62,8 @@ namespace Spectre.Console.Internal
}
_reader.Read();
return new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
Current = new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
return true;
}
var builder = new StringBuilder();
@ -80,7 +84,8 @@ namespace Spectre.Console.Internal
}
_reader.Read();
return new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
Current = new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
return true;
}
else
{
@ -97,7 +102,8 @@ namespace Spectre.Console.Internal
builder.Append(_reader.Read());
}
return new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
Current = new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
return true;
}
}
}