Add support for moving the cursor

This commit is contained in:
Patrik Svensson
2020-10-28 10:51:25 +01:00
committed by Patrik Svensson
parent 93d1971f48
commit a1d11e9d0c
29 changed files with 543 additions and 116 deletions

View File

@ -0,0 +1,82 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class AnsiBackend : IAnsiConsole
{
private readonly TextWriter _out;
private readonly AnsiBuilder _ansiBuilder;
private readonly AnsiCursor _cursor;
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public IAnsiConsoleCursor Cursor => _cursor;
public int Width
{
get
{
if (_out.IsStandardOut())
{
return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
}
return Constants.DefaultBufferWidth;
}
}
public int Height
{
get
{
if (_out.IsStandardOut())
{
return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
}
return Constants.DefaultBufferHeight;
}
}
public AnsiBackend(TextWriter @out, Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
{
_out = @out ?? throw new ArgumentNullException(nameof(@out));
Capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
Encoding = _out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
_cursor = new AnsiCursor(this);
}
public void Clear(bool home)
{
Write(Segment.Control("\u001b[2J"));
if (home)
{
Cursor.SetPosition(0, 0);
}
}
public void Write(Segment segment)
{
var parts = segment.Text.NormalizeLineEndings().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate())
{
if (!string.IsNullOrEmpty(part))
{
_out.Write(_ansiBuilder.GetAnsi(part, segment.Style));
}
if (!last)
{
_out.Write(Environment.NewLine);
}
}
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class AnsiCursor : IAnsiConsoleCursor
{
private readonly AnsiBackend _renderer;
public AnsiCursor(AnsiBackend renderer)
{
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
}
public void Show(bool show)
{
if (show)
{
_renderer.Write(Segment.Control("\u001b[?25h"));
}
else
{
_renderer.Write(Segment.Control("\u001b[?25l"));
}
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
_renderer.Write(Segment.Control($"\u001b[{steps}A"));
break;
case CursorDirection.Down:
_renderer.Write(Segment.Control($"\u001b[{steps}B"));
break;
case CursorDirection.Right:
_renderer.Write(Segment.Control($"\u001b[{steps}C"));
break;
case CursorDirection.Left:
_renderer.Write(Segment.Control($"\u001b[{steps}D"));
break;
}
}
public void SetPosition(int column, int line)
{
_renderer.Write(Segment.Control($"\u001b[{line};{column}H"));
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Runtime.InteropServices;
namespace Spectre.Console.Internal
{
internal static class BackendBuilder
{
public static IAnsiConsole Build(AnsiConsoleSettings settings)
{
if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}
var buffer = settings.Out ?? System.Console.Out;
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
var legacyConsole = false;
if (settings.Ansi == AnsiSupport.Detect)
{
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(true);
// Check whether or not this is a legacy console from the existing instance (if any).
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole)
{
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
}
}
else
{
if (buffer.IsStandardOut())
{
// Are we running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Not the first console we're creating?
if (AnsiConsole.Created)
{
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
}
else
{
// Try detecting whether or not this
(_, legacyConsole) = AnsiDetector.Detect(false);
}
}
}
}
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
? ColorSystemDetector.Detect(supportsAnsi)
: (ColorSystem)settings.ColorSystem;
// Get the capabilities
var capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
// Create the renderer
if (supportsAnsi)
{
return new AnsiBackend(buffer, capabilities, settings.LinkIdentityGenerator);
}
else
{
return new FallbackBackend(buffer, capabilities);
}
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class FallbackBackend : IAnsiConsole
{
private readonly ColorSystem _system;
private readonly FallbackCursor _cursor;
private Style? _lastStyle;
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public IAnsiConsoleCursor Cursor => _cursor;
public int Width
{
get { return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth); }
}
public int Height
{
get { return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight); }
}
public FallbackBackend(TextWriter @out, Capabilities capabilities)
{
if (capabilities == null)
{
throw new ArgumentNullException(nameof(capabilities));
}
_system = capabilities.ColorSystem;
_cursor = new FallbackCursor();
if (@out != System.Console.Out)
{
System.Console.SetOut(@out ?? throw new ArgumentNullException(nameof(@out)));
}
Encoding = System.Console.OutputEncoding;
Capabilities = capabilities;
}
public void Clear(bool home)
{
var (x, y) = (System.Console.CursorLeft, System.Console.CursorTop);
System.Console.Clear();
if (!home)
{
// Set the cursor position
System.Console.SetCursorPosition(x, y);
}
}
public void Write(Segment segment)
{
if (_lastStyle?.Equals(segment.Style) != true)
{
SetStyle(segment.Style);
}
System.Console.Write(segment.Text.NormalizeLineEndings(native: true));
}
private void SetStyle(Style style)
{
_lastStyle = style;
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_system != ColorSystem.NoColors && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_system != ColorSystem.NoColors && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
}
}
}

View File

@ -0,0 +1,40 @@
namespace Spectre.Console.Internal
{
internal sealed class FallbackCursor : IAnsiConsoleCursor
{
public void Show(bool show)
{
System.Console.CursorVisible = show;
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
System.Console.CursorTop -= steps;
break;
case CursorDirection.Down:
System.Console.CursorTop += steps;
break;
case CursorDirection.Left:
System.Console.CursorLeft -= steps;
break;
case CursorDirection.Right:
System.Console.CursorLeft += steps;
break;
}
}
public void SetPosition(int x, int y)
{
System.Console.CursorLeft = x;
System.Console.CursorTop = y;
}
}
}