Use file scoped namespace declarations

This commit is contained in:
Patrik Svensson
2021-12-21 11:06:46 +01:00
committed by Phil Scott
parent 1dbaf50935
commit ec1188b837
607 changed files with 28739 additions and 29245 deletions

View File

@ -4,108 +4,107 @@ using System.Text;
using Spectre.Console.Rendering;
using static Spectre.Console.AnsiSequences;
namespace Spectre.Console
namespace Spectre.Console;
internal static class AnsiBuilder
{
internal static class AnsiBuilder
private static readonly AnsiLinkHasher _linkHasher;
static AnsiBuilder()
{
private static readonly AnsiLinkHasher _linkHasher;
static AnsiBuilder()
{
_linkHasher = new AnsiLinkHasher();
}
public static string Build(IAnsiConsole console, IRenderable renderable)
{
var builder = new StringBuilder();
foreach (var segment in renderable.GetSegments(console))
{
if (segment.IsControlCode)
{
builder.Append(segment.Text);
continue;
}
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate())
{
if (!string.IsNullOrEmpty(part))
{
builder.Append(Build(console.Profile, part, segment.Style));
}
if (!last)
{
builder.Append(Environment.NewLine);
}
}
}
return builder.ToString();
}
private static string Build(Profile profile, string text, Style style)
{
if (profile is null)
{
throw new ArgumentNullException(nameof(profile));
}
else if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
else if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var codes = AnsiDecorationBuilder.GetAnsiCodes(style.Decoration);
// Got foreground?
if (style.Foreground != Color.Default)
{
codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
profile.Capabilities.ColorSystem,
style.Foreground,
true));
}
// Got background?
if (style.Background != Color.Default)
{
codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
profile.Capabilities.ColorSystem,
style.Background,
false));
}
var result = codes.ToArray();
if (result.Length == 0 && style.Link == null)
{
return text;
}
var ansi = result.Length > 0
? $"{SGR(result)}{text}{SGR(0)}"
: text;
if (style.Link != null && !profile.Capabilities.Legacy)
{
var link = style.Link;
// Empty links means we should take the URL from the text.
if (link.Equals(Constants.EmptyLink, StringComparison.Ordinal))
{
link = text;
}
var linkId = _linkHasher.GenerateId(link, text);
ansi = $"{ESC}]8;id={linkId};{link}{ESC}\\{ansi}{ESC}]8;;{ESC}\\";
}
return ansi;
}
_linkHasher = new AnsiLinkHasher();
}
}
public static string Build(IAnsiConsole console, IRenderable renderable)
{
var builder = new StringBuilder();
foreach (var segment in renderable.GetSegments(console))
{
if (segment.IsControlCode)
{
builder.Append(segment.Text);
continue;
}
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate())
{
if (!string.IsNullOrEmpty(part))
{
builder.Append(Build(console.Profile, part, segment.Style));
}
if (!last)
{
builder.Append(Environment.NewLine);
}
}
}
return builder.ToString();
}
private static string Build(Profile profile, string text, Style style)
{
if (profile is null)
{
throw new ArgumentNullException(nameof(profile));
}
else if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
else if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var codes = AnsiDecorationBuilder.GetAnsiCodes(style.Decoration);
// Got foreground?
if (style.Foreground != Color.Default)
{
codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
profile.Capabilities.ColorSystem,
style.Foreground,
true));
}
// Got background?
if (style.Background != Color.Default)
{
codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
profile.Capabilities.ColorSystem,
style.Background,
false));
}
var result = codes.ToArray();
if (result.Length == 0 && style.Link == null)
{
return text;
}
var ansi = result.Length > 0
? $"{SGR(result)}{text}{SGR(0)}"
: text;
if (style.Link != null && !profile.Capabilities.Legacy)
{
var link = style.Link;
// Empty links means we should take the URL from the text.
if (link.Equals(Constants.EmptyLink, StringComparison.Ordinal))
{
link = text;
}
var linkId = _linkHasher.GenerateId(link, text);
ansi = $"{ESC}]8;id={linkId};{link}{ESC}\\{ansi}{ESC}]8;;{ESC}\\";
}
return ansi;
}
}

View File

@ -2,69 +2,68 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Spectre.Console
namespace Spectre.Console;
internal static class AnsiColorBuilder
{
internal static class AnsiColorBuilder
public static IEnumerable<byte> GetAnsiCodes(ColorSystem system, Color color, bool foreground)
{
public static IEnumerable<byte> GetAnsiCodes(ColorSystem system, Color color, bool foreground)
return system switch
{
return system switch
{
ColorSystem.NoColors => Array.Empty<byte>(), // No colors
ColorSystem.TrueColor => GetTrueColor(color, foreground), // 24-bit
ColorSystem.EightBit => GetEightBit(color, foreground), // 8-bit
ColorSystem.Standard => GetFourBit(color, foreground), // 4-bit
ColorSystem.Legacy => GetThreeBit(color, foreground), // 3-bit
_ => throw new InvalidOperationException("Could not determine ANSI color."),
};
}
private static IEnumerable<byte> GetThreeBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 8)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Legacy, color).Number;
}
Debug.Assert(number >= 0 && number < 8, "Invalid range for 4-bit color");
var mod = foreground ? 30 : 40;
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetFourBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 16)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Standard, color).Number;
}
Debug.Assert(number >= 0 && number < 16, "Invalid range for 4-bit color");
var mod = number < 8 ? (foreground ? 30 : 40) : (foreground ? 82 : 92);
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetEightBit(Color color, bool foreground)
{
var number = color.Number ?? ColorPalette.ExactOrClosest(ColorSystem.EightBit, color).Number;
Debug.Assert(number >= 0 && number <= 255, "Invalid range for 8-bit color");
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 5, (byte)number };
}
private static IEnumerable<byte> GetTrueColor(Color color, bool foreground)
{
if (color.Number != null)
{
return GetEightBit(color, foreground);
}
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 2, color.R, color.G, color.B };
}
ColorSystem.NoColors => Array.Empty<byte>(), // No colors
ColorSystem.TrueColor => GetTrueColor(color, foreground), // 24-bit
ColorSystem.EightBit => GetEightBit(color, foreground), // 8-bit
ColorSystem.Standard => GetFourBit(color, foreground), // 4-bit
ColorSystem.Legacy => GetThreeBit(color, foreground), // 3-bit
_ => throw new InvalidOperationException("Could not determine ANSI color."),
};
}
}
private static IEnumerable<byte> GetThreeBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 8)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Legacy, color).Number;
}
Debug.Assert(number >= 0 && number < 8, "Invalid range for 4-bit color");
var mod = foreground ? 30 : 40;
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetFourBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 16)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Standard, color).Number;
}
Debug.Assert(number >= 0 && number < 16, "Invalid range for 4-bit color");
var mod = number < 8 ? (foreground ? 30 : 40) : (foreground ? 82 : 92);
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetEightBit(Color color, bool foreground)
{
var number = color.Number ?? ColorPalette.ExactOrClosest(ColorSystem.EightBit, color).Number;
Debug.Assert(number >= 0 && number <= 255, "Invalid range for 8-bit color");
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 5, (byte)number };
}
private static IEnumerable<byte> GetTrueColor(Color color, bool foreground)
{
if (color.Number != null)
{
return GetEightBit(color, foreground);
}
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 2, color.R, color.G, color.B };
}
}

View File

@ -2,39 +2,38 @@ using System;
using Spectre.Console.Rendering;
using static Spectre.Console.AnsiSequences;
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend
{
internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend
private readonly IAnsiConsole _console;
public IAnsiConsoleCursor Cursor { get; }
public AnsiConsoleBackend(IAnsiConsole console)
{
private readonly IAnsiConsole _console;
_console = console ?? throw new ArgumentNullException(nameof(console));
Cursor = new AnsiConsoleCursor(this);
}
public IAnsiConsoleCursor Cursor { get; }
public void Clear(bool home)
{
Write(new ControlCode(ED(2)));
Write(new ControlCode(ED(3)));
public AnsiConsoleBackend(IAnsiConsole console)
if (home)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
Cursor = new AnsiConsoleCursor(this);
}
public void Clear(bool home)
{
Write(new ControlCode(ED(2)));
Write(new ControlCode(ED(3)));
if (home)
{
Write(new ControlCode(CUP(1, 1)));
}
}
public void Write(IRenderable renderable)
{
var result = AnsiBuilder.Build(_console, renderable);
if (result?.Length > 0)
{
_console.Profile.Out.Writer.Write(result);
_console.Profile.Out.Writer.Flush();
}
Write(new ControlCode(CUP(1, 1)));
}
}
}
public void Write(IRenderable renderable)
{
var result = AnsiBuilder.Build(_console, renderable);
if (result?.Length > 0)
{
_console.Profile.Out.Writer.Write(result);
_console.Profile.Out.Writer.Flush();
}
}
}

View File

@ -1,56 +1,55 @@
using System;
using static Spectre.Console.AnsiSequences;
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class AnsiConsoleCursor : IAnsiConsoleCursor
{
internal sealed class AnsiConsoleCursor : IAnsiConsoleCursor
private readonly AnsiConsoleBackend _backend;
public AnsiConsoleCursor(AnsiConsoleBackend backend)
{
private readonly AnsiConsoleBackend _backend;
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
}
public AnsiConsoleCursor(AnsiConsoleBackend backend)
public void Show(bool show)
{
if (show)
{
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
_backend.Write(new ControlCode(SM(DECTCEM)));
}
public void Show(bool show)
else
{
if (show)
{
_backend.Write(new ControlCode(SM(DECTCEM)));
}
else
{
_backend.Write(new ControlCode(RM(DECTCEM)));
}
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
_backend.Write(new ControlCode(CUU(steps)));
break;
case CursorDirection.Down:
_backend.Write(new ControlCode(CUD(steps)));
break;
case CursorDirection.Right:
_backend.Write(new ControlCode(CUF(steps)));
break;
case CursorDirection.Left:
_backend.Write(new ControlCode(CUB(steps)));
break;
}
}
public void SetPosition(int column, int line)
{
_backend.Write(new ControlCode(CUP(line, column)));
_backend.Write(new ControlCode(RM(DECTCEM)));
}
}
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
_backend.Write(new ControlCode(CUU(steps)));
break;
case CursorDirection.Down:
_backend.Write(new ControlCode(CUD(steps)));
break;
case CursorDirection.Right:
_backend.Write(new ControlCode(CUF(steps)));
break;
case CursorDirection.Left:
_backend.Write(new ControlCode(CUB(steps)));
break;
}
}
public void SetPosition(int column, int line)
{
_backend.Write(new ControlCode(CUP(line, column)));
}
}

View File

@ -1,56 +1,55 @@
using System.Collections.Generic;
namespace Spectre.Console
namespace Spectre.Console;
internal static class AnsiDecorationBuilder
{
internal static class AnsiDecorationBuilder
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
{
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
if ((decoration & Decoration.Bold) != 0)
{
if ((decoration & Decoration.Bold) != 0)
{
yield return 1;
}
yield return 1;
}
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}
}
}
}

View File

@ -9,133 +9,132 @@ using System.Text.RegularExpressions;
// https://github.com/keqingrong/supports-ansi/blob/master/index.js
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Spectre.Console
namespace Spectre.Console;
internal static class AnsiDetector
{
internal static class AnsiDetector
private static readonly Regex[] _regexes = new[]
{
private static readonly Regex[] _regexes = new[]
{
new Regex("^xterm"), // xterm, PuTTY, Mintty
new Regex("^rxvt"), // RXVT
new Regex("^eterm"), // Eterm
new Regex("^screen"), // GNU screen, tmux
new Regex("tmux"), // tmux
new Regex("^vt100"), // DEC VT series
new Regex("^vt102"), // DEC VT series
new Regex("^vt220"), // DEC VT series
new Regex("^vt320"), // DEC VT series
new Regex("ansi"), // ANSI
new Regex("scoansi"), // SCO ANSI
new Regex("cygwin"), // Cygwin, MinGW
new Regex("linux"), // Linux console
new Regex("konsole"), // Konsole
new Regex("bvterm"), // Bitvise SSH Client
};
new Regex("^xterm"), // xterm, PuTTY, Mintty
new Regex("^rxvt"), // RXVT
new Regex("^eterm"), // Eterm
new Regex("^screen"), // GNU screen, tmux
new Regex("tmux"), // tmux
new Regex("^vt100"), // DEC VT series
new Regex("^vt102"), // DEC VT series
new Regex("^vt220"), // DEC VT series
new Regex("^vt320"), // DEC VT series
new Regex("ansi"), // ANSI
new Regex("scoansi"), // SCO ANSI
new Regex("cygwin"), // Cygwin, MinGW
new Regex("linux"), // Linux console
new Regex("konsole"), // Konsole
new Regex("bvterm"), // Bitvise SSH Client
};
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool stdError, bool upgrade)
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool stdError, bool upgrade)
{
// Running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
// Running under ConEmu?
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
{
// Running under ConEmu?
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
{
return (true, false);
}
var supportsAnsi = Windows.SupportsAnsi(upgrade, stdError, out var legacyConsole);
return (supportsAnsi, legacyConsole);
return (true, false);
}
return DetectFromTerm();
var supportsAnsi = Windows.SupportsAnsi(upgrade, stdError, out var legacyConsole);
return (supportsAnsi, legacyConsole);
}
private static (bool SupportsAnsi, bool LegacyConsole) DetectFromTerm()
return DetectFromTerm();
}
private static (bool SupportsAnsi, bool LegacyConsole) DetectFromTerm()
{
// Check if the terminal is of type ANSI/VT100/xterm compatible.
var term = Environment.GetEnvironmentVariable("TERM");
if (!string.IsNullOrWhiteSpace(term))
{
// Check if the terminal is of type ANSI/VT100/xterm compatible.
var term = Environment.GetEnvironmentVariable("TERM");
if (!string.IsNullOrWhiteSpace(term))
if (_regexes.Any(regex => regex.IsMatch(term)))
{
if (_regexes.Any(regex => regex.IsMatch(term)))
{
return (true, false);
}
return (true, false);
}
return (false, true);
}
internal static class Windows
return (false, true);
}
internal static class Windows
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const int STD_OUTPUT_HANDLE = -11;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const int STD_ERROR_HANDLE = -12;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
[DllImport("kernel32.dll")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
public static bool SupportsAnsi(bool upgrade, bool stdError, out bool isLegacy)
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const int STD_OUTPUT_HANDLE = -11;
isLegacy = false;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const int STD_ERROR_HANDLE = -12;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
[DllImport("kernel32.dll")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
public static bool SupportsAnsi(bool upgrade, bool stdError, out bool isLegacy)
try
{
isLegacy = false;
try
var @out = GetStdHandle(stdError ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
if (!GetConsoleMode(@out, out var mode))
{
var @out = GetStdHandle(stdError ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
if (!GetConsoleMode(@out, out var mode))
{
// Could not get console mode, try TERM (set in cygwin, WSL-Shell).
var (ansiFromTerm, legacyFromTerm) = DetectFromTerm();
// Could not get console mode, try TERM (set in cygwin, WSL-Shell).
var (ansiFromTerm, legacyFromTerm) = DetectFromTerm();
isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy;
return ansiFromTerm;
isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy;
return ansiFromTerm;
}
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
{
isLegacy = true;
if (!upgrade)
{
return false;
}
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
// Try enable ANSI support.
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
if (!SetConsoleMode(@out, mode))
{
isLegacy = true;
if (!upgrade)
{
return false;
}
// Try enable ANSI support.
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
if (!SetConsoleMode(@out, mode))
{
// Enabling failed.
return false;
}
isLegacy = false;
// Enabling failed.
return false;
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
isLegacy = false;
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
}
}
}
}
}

View File

@ -1,42 +1,41 @@
using System;
using System.Runtime.CompilerServices;
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class AnsiLinkHasher
{
internal sealed class AnsiLinkHasher
private readonly Random _random;
public AnsiLinkHasher()
{
private readonly Random _random;
_random = new Random(Environment.TickCount);
}
public AnsiLinkHasher()
public int GenerateId(string link, string text)
{
if (link is null)
{
_random = new Random(Environment.TickCount);
throw new ArgumentNullException(nameof(link));
}
public int GenerateId(string link, string text)
link += text ?? string.Empty;
unchecked
{
if (link is null)
{
throw new ArgumentNullException(nameof(link));
}
link += text ?? string.Empty;
unchecked
{
return Math.Abs(
GetLinkHashCode(link) +
_random.Next(0, int.MaxValue));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLinkHashCode(string link)
{
#if NETSTANDARD2_0
return link.GetHashCode();
#else
return link.GetHashCode(StringComparison.Ordinal);
#endif
return Math.Abs(
GetLinkHashCode(link) +
_random.Next(0, int.MaxValue));
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLinkHashCode(string link)
{
#if NETSTANDARD2_0
return link.GetHashCode();
#else
return link.GetHashCode(StringComparison.Ordinal);
#endif
}
}

View File

@ -1,163 +1,162 @@
using System.Linq;
namespace Spectre.Console
namespace Spectre.Console;
internal static class AnsiSequences
{
internal static class AnsiSequences
/// <summary>
/// The ASCII escape character (decimal 27).
/// </summary>
public const string ESC = "\u001b";
/// <summary>
/// Introduces a control sequence that uses 8-bit characters.
/// </summary>
public const string CSI = ESC + "[";
/// <summary>
/// Text cursor enable.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/DECRQM.html#T5-8"/>.
/// </remarks>
public const int DECTCEM = 25;
/// <summary>
/// This control function selects one or more character attributes at the same time.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/SGR.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string SGR(params byte[] codes)
{
/// <summary>
/// The ASCII escape character (decimal 27).
/// </summary>
public const string ESC = "\u001b";
/// <summary>
/// Introduces a control sequence that uses 8-bit characters.
/// </summary>
public const string CSI = ESC + "[";
/// <summary>
/// Text cursor enable.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/DECRQM.html#T5-8"/>.
/// </remarks>
public const int DECTCEM = 25;
/// <summary>
/// This control function selects one or more character attributes at the same time.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/SGR.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string SGR(params byte[] codes)
{
var joinedCodes = string.Join(";", codes.Select(c => c.ToString()));
return $"{CSI}{joinedCodes}m";
}
/// <summary>
/// This control function erases characters from part or all of the display.
/// When you erase complete lines, they become single-height, single-width lines,
/// with all visual character attributes cleared.
/// ED works inside or outside the scrolling margins.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/ED.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string ED(int code)
{
return $"{CSI}{code}J";
}
/// <summary>
/// Moves the cursor up a specified number of lines in the same column.
/// The cursor stops at the top margin.
/// If the cursor is already above the top margin, then the cursor stops at the top line.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUU.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move up.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUU(int steps)
{
return $"{CSI}{steps}A";
}
/// <summary>
/// This control function moves the cursor down a specified number of lines in the same column.
/// The cursor stops at the bottom margin.
/// If the cursor is already below the bottom margin, then the cursor stops at the bottom line.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUD.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move down.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUD(int steps)
{
return $"{CSI}{steps}B";
}
/// <summary>
/// This control function moves the cursor to the right by a specified number of columns.
/// The cursor stops at the right border of the page.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUF.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move forward.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUF(int steps)
{
return $"{CSI}{steps}C";
}
/// <summary>
/// This control function moves the cursor to the left by a specified number of columns.
/// The cursor stops at the left border of the page.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUB.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move backward.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUB(int steps)
{
return $"{CSI}{steps}D";
}
/// <summary>
/// Moves the cursor to the specified position.
/// </summary>
/// <param name="line">The line to move to.</param>
/// <param name="column">The column to move to.</param>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUP.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string CUP(int line, int column)
{
return $"{CSI}{line};{column}H";
}
/// <summary>
/// Hides the cursor.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/RM.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string RM(int code)
{
return $"{CSI}?{code}l";
}
/// <summary>
/// Shows the cursor.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/SM.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string SM(int code)
{
return $"{CSI}?{code}h";
}
/// <summary>
/// This control function erases characters on the line that has the cursor.
/// EL clears all character attributes from erased character positions.
/// EL works inside or outside the scrolling margins.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/EL.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string EL(int code)
{
return $"{CSI}{code}K";
}
var joinedCodes = string.Join(";", codes.Select(c => c.ToString()));
return $"{CSI}{joinedCodes}m";
}
}
/// <summary>
/// This control function erases characters from part or all of the display.
/// When you erase complete lines, they become single-height, single-width lines,
/// with all visual character attributes cleared.
/// ED works inside or outside the scrolling margins.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/ED.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string ED(int code)
{
return $"{CSI}{code}J";
}
/// <summary>
/// Moves the cursor up a specified number of lines in the same column.
/// The cursor stops at the top margin.
/// If the cursor is already above the top margin, then the cursor stops at the top line.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUU.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move up.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUU(int steps)
{
return $"{CSI}{steps}A";
}
/// <summary>
/// This control function moves the cursor down a specified number of lines in the same column.
/// The cursor stops at the bottom margin.
/// If the cursor is already below the bottom margin, then the cursor stops at the bottom line.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUD.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move down.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUD(int steps)
{
return $"{CSI}{steps}B";
}
/// <summary>
/// This control function moves the cursor to the right by a specified number of columns.
/// The cursor stops at the right border of the page.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUF.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move forward.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUF(int steps)
{
return $"{CSI}{steps}C";
}
/// <summary>
/// This control function moves the cursor to the left by a specified number of columns.
/// The cursor stops at the left border of the page.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUB.html"/>.
/// </remarks>
/// <param name="steps">The number of steps to move backward.</param>
/// <returns>The ANSI escape code.</returns>
public static string CUB(int steps)
{
return $"{CSI}{steps}D";
}
/// <summary>
/// Moves the cursor to the specified position.
/// </summary>
/// <param name="line">The line to move to.</param>
/// <param name="column">The column to move to.</param>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/CUP.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string CUP(int line, int column)
{
return $"{CSI}{line};{column}H";
}
/// <summary>
/// Hides the cursor.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/RM.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string RM(int code)
{
return $"{CSI}?{code}l";
}
/// <summary>
/// Shows the cursor.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/SM.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string SM(int code)
{
return $"{CSI}?{code}h";
}
/// <summary>
/// This control function erases characters on the line that has the cursor.
/// EL clears all character attributes from erased character positions.
/// EL works inside or outside the scrolling margins.
/// </summary>
/// <remarks>
/// See <see href="https://vt100.net/docs/vt510-rm/EL.html"/>.
/// </remarks>
/// <returns>The ANSI escape code.</returns>
public static string EL(int code)
{
return $"{CSI}{code}K";
}
}

View File

@ -1,57 +1,56 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class AnsiConsoleFacade : IAnsiConsole
{
internal sealed class AnsiConsoleFacade : IAnsiConsole
private readonly object _renderLock;
private readonly AnsiConsoleBackend _ansiBackend;
private readonly LegacyConsoleBackend _legacyBackend;
public Profile Profile { get; }
public IAnsiConsoleCursor Cursor => GetBackend().Cursor;
public IAnsiConsoleInput Input { get; }
public IExclusivityMode ExclusivityMode { get; }
public RenderPipeline Pipeline { get; }
public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode)
{
private readonly object _renderLock;
private readonly AnsiConsoleBackend _ansiBackend;
private readonly LegacyConsoleBackend _legacyBackend;
_renderLock = new object();
public Profile Profile { get; }
public IAnsiConsoleCursor Cursor => GetBackend().Cursor;
public IAnsiConsoleInput Input { get; }
public IExclusivityMode ExclusivityMode { get; }
public RenderPipeline Pipeline { get; }
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
Input = new DefaultInput(Profile);
ExclusivityMode = exclusivityMode ?? throw new ArgumentNullException(nameof(exclusivityMode));
Pipeline = new RenderPipeline();
public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode)
_ansiBackend = new AnsiConsoleBackend(this);
_legacyBackend = new LegacyConsoleBackend(this);
}
public void Clear(bool home)
{
lock (_renderLock)
{
_renderLock = new object();
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
Input = new DefaultInput(Profile);
ExclusivityMode = exclusivityMode ?? throw new ArgumentNullException(nameof(exclusivityMode));
Pipeline = new RenderPipeline();
_ansiBackend = new AnsiConsoleBackend(this);
_legacyBackend = new LegacyConsoleBackend(this);
}
public void Clear(bool home)
{
lock (_renderLock)
{
GetBackend().Clear(home);
}
}
public void Write(IRenderable renderable)
{
lock (_renderLock)
{
GetBackend().Write(renderable);
}
}
private IAnsiConsoleBackend GetBackend()
{
if (Profile.Capabilities.Ansi)
{
return _ansiBackend;
}
return _legacyBackend;
GetBackend().Clear(home);
}
}
}
public void Write(IRenderable renderable)
{
lock (_renderLock)
{
GetBackend().Write(renderable);
}
}
private IAnsiConsoleBackend GetBackend()
{
if (Profile.Capabilities.Ansi)
{
return _ansiBackend;
}
return _legacyBackend;
}
}

View File

@ -1,27 +1,26 @@
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents a console backend.
/// </summary>
internal interface IAnsiConsoleBackend
{
/// <summary>
/// Represents a console backend.
/// Gets the console cursor for the backend.
/// </summary>
internal interface IAnsiConsoleBackend
{
/// <summary>
/// Gets the console cursor for the backend.
/// </summary>
IAnsiConsoleCursor Cursor { get; }
IAnsiConsoleCursor Cursor { get; }
/// <summary>
/// Clears the console.
/// </summary>
/// <param name="home">If the cursor should be moved to the home position.</param>
void Clear(bool home);
/// <summary>
/// Clears the console.
/// </summary>
/// <param name="home">If the cursor should be moved to the home position.</param>
void Clear(bool home);
/// <summary>
/// Writes a <see cref="IRenderable"/> to the console backend.
/// </summary>
/// <param name="renderable">The <see cref="IRenderable"/> to write.</param>
void Write(IRenderable renderable);
}
}
/// <summary>
/// Writes a <see cref="IRenderable"/> to the console backend.
/// </summary>
/// <param name="renderable">The <see cref="IRenderable"/> to write.</param>
void Write(IRenderable renderable);
}

View File

@ -1,70 +1,69 @@
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class LegacyConsoleBackend : IAnsiConsoleBackend
{
internal sealed class LegacyConsoleBackend : IAnsiConsoleBackend
private readonly IAnsiConsole _console;
private Style _lastStyle;
public IAnsiConsoleCursor Cursor { get; }
public LegacyConsoleBackend(IAnsiConsole console)
{
private readonly IAnsiConsole _console;
private Style _lastStyle;
_console = console ?? throw new System.ArgumentNullException(nameof(console));
_lastStyle = Style.Plain;
public IAnsiConsoleCursor Cursor { get; }
Cursor = new LegacyConsoleCursor();
}
public LegacyConsoleBackend(IAnsiConsole console)
public void Clear(bool home)
{
var (x, y) = (System.Console.CursorLeft, System.Console.CursorTop);
System.Console.Clear();
if (!home)
{
_console = console ?? throw new System.ArgumentNullException(nameof(console));
_lastStyle = Style.Plain;
Cursor = new LegacyConsoleCursor();
}
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(IRenderable renderable)
{
foreach (var segment in renderable.GetSegments(_console))
{
if (segment.IsControlCode)
{
continue;
}
if (_lastStyle?.Equals(segment.Style) != true)
{
SetStyle(segment.Style);
}
_console.Profile.Out.Writer.Write(segment.Text.NormalizeNewLines(native: true));
}
}
private void SetStyle(Style style)
{
_lastStyle = style;
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_console.Profile.Capabilities.ColorSystem != ColorSystem.NoColors && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_console.Profile.Capabilities.ColorSystem != ColorSystem.NoColors && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
// Set the cursor position
System.Console.SetCursorPosition(x, y);
}
}
}
public void Write(IRenderable renderable)
{
foreach (var segment in renderable.GetSegments(_console))
{
if (segment.IsControlCode)
{
continue;
}
if (_lastStyle?.Equals(segment.Style) != true)
{
SetStyle(segment.Style);
}
_console.Profile.Out.Writer.Write(segment.Text.NormalizeNewLines(native: true));
}
}
private void SetStyle(Style style)
{
_lastStyle = style;
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_console.Profile.Capabilities.ColorSystem != ColorSystem.NoColors && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_console.Profile.Capabilities.ColorSystem != ColorSystem.NoColors && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
}
}

View File

@ -1,40 +1,39 @@
namespace Spectre.Console
namespace Spectre.Console;
internal sealed class LegacyConsoleCursor : IAnsiConsoleCursor
{
internal sealed class LegacyConsoleCursor : IAnsiConsoleCursor
public void Show(bool show)
{
public void Show(bool show)
System.Console.CursorVisible = show;
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
System.Console.CursorVisible = show;
return;
}
public void Move(CursorDirection direction, int steps)
switch (direction)
{
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;
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;
}
}