mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00
Add support for moving the cursor
This commit is contained in:

committed by
Patrik Svensson

parent
93d1971f48
commit
a1d11e9d0c
82
src/Spectre.Console/Internal/Backends/Ansi/AnsiBackend.cs
Normal file
82
src/Spectre.Console/Internal/Backends/Ansi/AnsiBackend.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
src/Spectre.Console/Internal/Backends/Ansi/AnsiCursor.cs
Normal file
56
src/Spectre.Console/Internal/Backends/Ansi/AnsiCursor.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
71
src/Spectre.Console/Internal/Backends/BackendBuilder.cs
Normal file
71
src/Spectre.Console/Internal/Backends/BackendBuilder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user