Add progress task list support

This commit is contained in:
Patrik Svensson
2020-11-27 08:12:29 +01:00
committed by Patrik Svensson
parent c61e386440
commit ae32785f21
71 changed files with 2350 additions and 106 deletions

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Represents a render hook.
/// </summary>
public interface IRenderHook
{
/// <summary>
/// Processes the specified renderables.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="renderables">The renderables to process.</param>
/// <returns>The processed renderables.</returns>
IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables);
}
}

View File

@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console.Rendering
{
internal sealed class LiveRenderable : Renderable
{
private readonly object _lock = new object();
private IRenderable? _renderable;
private int? _height;
public void SetRenderable(IRenderable renderable)
{
lock (_lock)
{
_renderable = renderable;
}
}
public IRenderable PositionCursor()
{
lock (_lock)
{
if (_height == null)
{
return new ControlSequence(string.Empty);
}
return new ControlSequence("\r" + "\u001b[1A".Repeat(_height.Value - 1));
}
}
public IRenderable RestoreCursor()
{
lock (_lock)
{
if (_height == null)
{
return new ControlSequence(string.Empty);
}
return new ControlSequence("\r\u001b[2K" + "\u001b[1A\u001b[2K".Repeat(_height.Value - 1));
}
}
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
lock (_lock)
{
if (_renderable != null)
{
var segments = _renderable.Render(context, maxWidth);
var lines = Segment.SplitLines(context, segments);
_height = lines.Count;
var result = new List<Segment>();
foreach (var (_, _, last, line) in lines.Enumerate())
{
foreach (var item in line)
{
result.Add(item);
}
if (!last)
{
result.Add(Segment.LineBreak);
}
}
return result;
}
_height = 0;
return Enumerable.Empty<Segment>();
}
}
}
}

View File

@ -49,7 +49,7 @@ namespace Spectre.Console.Rendering
Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
LegacyConsole = legacyConsole;
Justification = justification;
Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode;
Unicode = Encoding.EncodingName.ContainsExact("Unicode");
SingleLine = singleLine;
}

View File

@ -0,0 +1,31 @@
using System;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Represents a render hook scope.
/// </summary>
public sealed class RenderHookScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly IRenderHook _hook;
/// <summary>
/// Initializes a new instance of the <see cref="RenderHookScope"/> class.
/// </summary>
/// <param name="console">The console to attach the render hook to.</param>
/// <param name="hook">The render hook.</param>
public RenderHookScope(IAnsiConsole console, IRenderHook hook)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_hook = hook ?? throw new ArgumentNullException(nameof(hook));
_console.Pipeline.Attach(_hook);
}
/// <inheritdoc/>
public void Dispose()
{
_console.Pipeline.Detach(_hook);
}
}
}

View File

@ -0,0 +1,66 @@
using System.Collections.Generic;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Represents the render pipeline.
/// </summary>
public sealed class RenderPipeline
{
private readonly List<IRenderHook> _hooks;
private readonly object _lock;
/// <summary>
/// Initializes a new instance of the <see cref="RenderPipeline"/> class.
/// </summary>
public RenderPipeline()
{
_hooks = new List<IRenderHook>();
_lock = new object();
}
/// <summary>
/// Attaches a new render hook onto the pipeline.
/// </summary>
/// <param name="hook">The render hook to attach.</param>
public void Attach(IRenderHook hook)
{
lock (_lock)
{
_hooks.Add(hook);
}
}
/// <summary>
/// Detaches a render hook from the pipeline.
/// </summary>
/// <param name="hook">The render hook to detach.</param>
public void Detach(IRenderHook hook)
{
lock (_lock)
{
_hooks.Remove(hook);
}
}
/// <summary>
/// Processes the specified renderables.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="renderables">The renderables to process.</param>
/// <returns>The processed renderables.</returns>
public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
{
lock (_lock)
{
var current = renderables;
for (var index = _hooks.Count - 1; index >= 0; index--)
{
current = _hooks[index].Process(context, current);
}
return current;
}
}
}
}

View File

@ -72,7 +72,7 @@ namespace Spectre.Console.Rendering
private Segment(string text, Style style, bool lineBreak, bool control)
{
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
Text = text?.NormalizeNewLines() ?? throw new ArgumentNullException(nameof(text));
Style = style ?? throw new ArgumentNullException(nameof(style));
IsLineBreak = lineBreak;
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
@ -102,6 +102,11 @@ namespace Spectre.Console.Rendering
throw new ArgumentNullException(nameof(context));
}
if (IsControlCode)
{
return 0;
}
return Text.CellLength(context);
}
@ -477,16 +482,22 @@ namespace Spectre.Console.Rendering
continue;
}
// Both control codes?
if (segment.IsControlCode && previous.IsControlCode)
{
previous = Control(previous.Text + segment.Text);
continue;
}
// Same style?
if (previous.Style.Equals(segment.Style) && !previous.IsLineBreak)
if (previous.Style.Equals(segment.Style) && !previous.IsLineBreak && !previous.IsControlCode)
{
previous = new Segment(previous.Text + segment.Text, previous.Style);
continue;
}
else
{
result.Add(previous);
previous = segment;
}
result.Add(previous);
previous = segment;
}
if (previous != null)