Initial commit

This commit is contained in:
Patrik Svensson
2020-07-21 12:03:41 +02:00
commit 334dcddc1a
57 changed files with 4685 additions and 0 deletions

View File

@ -0,0 +1,44 @@
using System.Linq;
namespace Spectre.Console.Internal
{
internal static class AnsiBuilder
{
public static string GetAnsi(
ColorSystem system,
string text,
Styles style,
Color foreground,
Color background)
{
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
// Got foreground?
if (foreground != Color.Default)
{
codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, foreground, foreground: true));
}
// Got background?
if (background != Color.Default)
{
codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, background, foreground: false));
}
var result = codes.ToArray();
if (result.Length == 0)
{
return text;
}
var lol = string.Concat(
"\u001b[",
string.Join(";", result),
"m",
text,
"\u001b[0m");
return lol;
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Spectre.Console.Internal
{
internal static class AnsiColorBuilder
{
public static IEnumerable<byte> GetAnsiCodes(ColorSystem system, Color color, bool foreground)
{
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 };
}
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Portions of this code was ported from the supports-ansi project by Qingrong Ke
// https://github.com/keqingrong/supports-ansi/blob/master/index.js
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Spectre.Console.Internal
{
internal static class AnsiDetector
{
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
};
public static bool SupportsAnsi(bool upgrade)
{
// Github action doesn't setup a correct PTY but supports ANSI.
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
{
return true;
}
// 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))
{
return true;
}
return Windows.SupportsAnsi(upgrade);
}
// 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)))
{
return true;
}
}
return false;
}
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
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 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();
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public static bool SupportsAnsi(bool upgrade)
{
try
{
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
if (!GetConsoleMode(@out, out uint mode))
{
// Could not get console mode.
return false;
}
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
{
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;
}
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
}
}
}
}
}

View File

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

View File

@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Internal
{
internal static class ColorPalette
{
public static IReadOnlyList<Color> Legacy { get; }
public static IReadOnlyList<Color> Standard { get; }
public static IReadOnlyList<Color> EightBit { get; }
static ColorPalette()
{
Legacy = new List<Color>
{
Color.Black, Color.Maroon, Color.Green, Color.Olive,
Color.Navy, Color.Purple, Color.Teal, Color.Silver,
};
Standard = new List<Color>(Legacy)
{
Color.Grey, Color.Red, Color.Lime, Color.Yellow,
Color.Blue, Color.Fuchsia, Color.Aqua, Color.White,
};
EightBit = new List<Color>(Standard)
{
Color.Grey0, Color.NavyBlue, Color.DarkBlue, Color.Blue3,
Color.Blue3_1, Color.Blue1, Color.DarkGreen, Color.DeepSkyBlue4,
Color.DeepSkyBlue4_1, Color.DeepSkyBlue4_2, Color.DodgerBlue3, Color.DodgerBlue2,
Color.Green4, Color.SpringGreen4, Color.Turquoise4, Color.DeepSkyBlue3,
Color.DeepSkyBlue3_1, Color.DodgerBlue1, Color.Green3, Color.SpringGreen3,
Color.DarkCyan, Color.LightSeaGreen, Color.DeepSkyBlue2, Color.DeepSkyBlue1,
Color.Green3_1, Color.SpringGreen3_1, Color.SpringGreen2, Color.Cyan3,
Color.DarkTurquoise, Color.Turquoise2, Color.Green1, Color.SpringGreen2_1,
Color.SpringGreen1, Color.MediumSpringGreen, Color.Cyan2, Color.Cyan1,
Color.DarkRed, Color.DeepPink4, Color.Purple4, Color.Purple4_1,
Color.Purple3, Color.BlueViolet, Color.Orange4, Color.Grey37,
Color.MediumPurple4, Color.SlateBlue3, Color.SlateBlue3_1, Color.RoyalBlue1,
Color.Chartreuse4, Color.DarkSeaGreen4, Color.PaleTurquoise4, Color.SteelBlue,
Color.SteelBlue3, Color.CornflowerBlue, Color.Chartreuse3, Color.DarkSeaGreen4_1,
Color.CadetBlue, Color.CadetBlue_1, Color.SkyBlue3, Color.SteelBlue1,
Color.Chartreuse3_1, Color.PaleGreen3, Color.SeaGreen3, Color.Aquamarine3,
Color.MediumTurquoise, Color.SteelBlue1_1, Color.Chartreuse2, Color.SeaGreen2,
Color.SeaGreen1, Color.SeaGreen1_1, Color.Aquamarine1, Color.DarkSlateGray2,
Color.DarkRed_1, Color.DeepPink4_1, Color.DarkMagenta, Color.DarkMagenta_1,
Color.DarkViolet, Color.Purple_1, Color.Orange4_1, Color.LightPink4,
Color.Plum4, Color.MediumPurple3, Color.MediumPurple3_1, Color.SlateBlue1,
Color.Yellow4, Color.Wheat4, Color.Grey53, Color.LightSlateGrey,
Color.MediumPurple, Color.LightSlateBlue, Color.Yellow4_1, Color.DarkOliveGreen3,
Color.DarkSeaGreen, Color.LightSkyBlue3, Color.LightSkyBlue3_1, Color.SkyBlue2,
Color.Chartreuse2_1, Color.DarkOliveGreen3_1, Color.PaleGreen3_1, Color.DarkSeaGreen3,
Color.DarkSlateGray3, Color.SkyBlue1, Color.Chartreuse1, Color.LightGreen,
Color.LightGreen_1, Color.PaleGreen1, Color.Aquamarine1_1, Color.DarkSlateGray1,
Color.Red3, Color.DeepPink4_2, Color.MediumVioletRed, Color.Magenta3,
Color.DarkViolet_1, Color.Purple_2, Color.DarkOrange3, Color.IndianRed,
Color.HotPink3, Color.MediumOrchid3, Color.MediumOrchid, Color.MediumPurple2,
Color.DarkGoldenrod, Color.LightSalmon3, Color.RosyBrown, Color.Grey63,
Color.MediumPurple2_1, Color.MediumPurple1, Color.Gold3, Color.DarkKhaki,
Color.NavajoWhite3, Color.Grey69, Color.LightSteelBlue3, Color.LightSteelBlue,
Color.Yellow3, Color.DarkOliveGreen3_2, Color.DarkSeaGreen3_1, Color.DarkSeaGreen2,
Color.LightCyan3, Color.LightSkyBlue1, Color.GreenYellow, Color.DarkOliveGreen2,
Color.PaleGreen1_1, Color.DarkSeaGreen2_1, Color.DarkSeaGreen1, Color.PaleTurquoise1,
Color.Red3_1, Color.DeepPink3, Color.DeepPink3_1, Color.Magenta3_1,
Color.Magenta3_2, Color.Magenta2, Color.DarkOrange3_1, Color.IndianRed_1,
Color.HotPink3_1, Color.HotPink2, Color.Orchid, Color.MediumOrchid1,
Color.Orange3, Color.LightSalmon3_1, Color.LightPink3, Color.Pink3,
Color.Plum3, Color.Violet, Color.Gold3_1, Color.LightGoldenrod3,
Color.Tan, Color.MistyRose3, Color.Thistle3, Color.Plum2,
Color.Yellow3_1, Color.Khaki3, Color.LightGoldenrod2, Color.LightYellow3,
Color.Grey84, Color.LightSteelBlue1, Color.Yellow2, Color.DarkOliveGreen1,
Color.DarkOliveGreen1_1, Color.DarkSeaGreen1_1, Color.Honeydew2, Color.LightCyan1,
Color.Red1, Color.DeepPink2, Color.DeepPink1, Color.DeepPink1_1,
Color.Magenta2_1, Color.Magenta1, Color.OrangeRed1, Color.IndianRed1,
Color.IndianRed1_1, Color.HotPink, Color.HotPink_1, Color.MediumOrchid1_1,
Color.DarkOrange, Color.Salmon1, Color.LightCoral, Color.PaleVioletRed1,
Color.Orchid2, Color.Orchid1, Color.Orange1, Color.SandyBrown,
Color.LightSalmon1, Color.LightPink1, Color.Pink1, Color.Plum1,
Color.Gold1, Color.LightGoldenrod2_1, Color.LightGoldenrod2_2, Color.NavajoWhite1,
Color.MistyRose1, Color.Thistle1, Color.Yellow1, Color.LightGoldenrod1,
Color.Khaki1, Color.Wheat1, Color.Cornsilk1, Color.Grey100,
Color.Grey3, Color.Grey7, Color.Grey11, Color.Grey15,
Color.Grey19, Color.Grey23, Color.Grey27, Color.Grey30,
Color.Grey35, Color.Grey39, Color.Grey42, Color.Grey46,
Color.Grey50, Color.Grey54, Color.Grey58, Color.Grey62,
Color.Grey66, Color.Grey70, Color.Grey74, Color.Grey78,
Color.Grey82, Color.Grey85, Color.Grey89, Color.Grey93,
};
}
internal static Color ExactOrClosest(ColorSystem system, Color color)
{
var exact = Exact(system, color);
if (exact != null)
{
return exact.Value;
}
return Closest(system, color);
}
private static Color? Exact(ColorSystem system, Color color)
{
if (system == ColorSystem.TrueColor)
{
return color;
}
var palette = system switch
{
ColorSystem.Legacy => Legacy,
ColorSystem.Standard => Standard,
ColorSystem.EightBit => EightBit,
_ => throw new NotSupportedException(),
};
return palette
.Where(c => c.Equals(color))
.Cast<Color?>()
.FirstOrDefault();
}
private static Color Closest(ColorSystem system, Color color)
{
if (system == ColorSystem.TrueColor)
{
return color;
}
var palette = system switch
{
ColorSystem.Legacy => Legacy,
ColorSystem.Standard => Standard,
ColorSystem.EightBit => EightBit,
_ => throw new NotSupportedException(),
};
// https://stackoverflow.com/a/9085524
static double Distance(Color first, Color second)
{
var rmean = ((float)first.R + second.R) / 2;
var r = first.R - second.R;
var g = first.G - second.G;
var b = first.B - second.B;
return Math.Sqrt(
((int)((512 + rmean) * r * r) >> 8)
+ (4 * g * g)
+ ((int)((767 - rmean) * b * b) >> 8));
}
return Enumerable.Range(0, int.MaxValue)
.Zip(palette, (id, other) => (Distance: Distance(other, color), Id: id, Color: other))
.OrderBy(x => x.Distance)
.FirstOrDefault().Color;
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace Spectre.Console.Internal
{
internal static class ColorSystemDetector
{
// Adapted from https://github.com/willmcgugan/rich/blob/f0c29052c22d1e49579956a9207324d9072beed7/rich/console.py#L391
// which I think is battletested enough to trust.
public static ColorSystem Detect(bool supportsAnsi)
{
if (Environment.GetEnvironmentVariables().Contains("NO_COLOR"))
{
// No colors supported
return ColorSystem.NoColors;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (supportsAnsi)
{
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)$");
var match = regex.Match(RuntimeInformation.OSDescription);
if (match.Success && int.TryParse(match.Groups["major"].Value, out var major))
{
if (major > 10)
{
// Future Patrik will thank me.
return ColorSystem.TrueColor;
}
if (major == 10 && int.TryParse(match.Groups["build"].Value, out var build) && build >= 15063)
{
return ColorSystem.TrueColor;
}
}
}
}
else
{
var colorTerm = Environment.GetEnvironmentVariable("COLORTERM");
if (!string.IsNullOrWhiteSpace(colorTerm))
{
if (colorTerm.Equals("truecolor", StringComparison.OrdinalIgnoreCase) ||
colorTerm.Equals("24bit", StringComparison.OrdinalIgnoreCase))
{
return ColorSystem.TrueColor;
}
}
}
return ColorSystem.EightBit;
}
}
}

View File

@ -0,0 +1,73 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class Composer : IRenderable
{
private readonly BlockElement _root;
/// <inheritdoc/>
public int Length => _root.Length;
public Composer()
{
_root = new BlockElement();
}
public static Composer New()
{
return new Composer();
}
public Composer Text(string text)
{
_root.Append(new TextElement(text));
return this;
}
public Composer Foreground(Color color, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new ForegroundElement(color, content));
return this;
}
public Composer Background(Color color, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new BackgroundElement(color, content));
return this;
}
public Composer Style(Styles style, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new StyleElement(style, content));
return this;
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
_root.Render(renderer);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class BackgroundElement : IRenderable
{
private readonly Color _color;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public BackgroundElement(Color color, IRenderable element)
{
_color = color;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushColor(_color, foreground: false))
{
_element.Render(renderer);
}
}
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class BlockElement : IRenderable
{
private readonly List<IRenderable> _elements;
/// <inheritdoc/>
public int Length { get; private set; }
public IReadOnlyList<IRenderable> Elements => _elements;
public BlockElement()
{
_elements = new List<IRenderable>();
}
public BlockElement Append(IRenderable element)
{
if (element != null)
{
_elements.Add(element);
Length += element.Length;
}
return this;
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
foreach (var element in _elements)
{
element.Render(renderer);
}
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class ForegroundElement : IRenderable
{
private readonly Color _color;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public ForegroundElement(Color color, IRenderable element)
{
_color = color;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushColor(_color, foreground: true))
{
_element.Render(renderer);
}
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class LineBreakElement : IRenderable
{
/// <inheritdoc/>
public int Length => 0;
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
renderer.Write(Environment.NewLine);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class StyleElement : IRenderable
{
private readonly Styles _style;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public StyleElement(Styles style, IRenderable element)
{
_style = style;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushStyle(_style))
{
_element.Render(renderer);
}
}
}
}

View File

@ -0,0 +1,24 @@
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class TextElement : IRenderable
{
private readonly string _text;
/// <inheritdoc/>
public int Length => _text.Length;
public TextElement(string text)
{
_text = text ?? throw new System.ArgumentNullException(nameof(text));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
renderer.Write(_text);
}
}
}

View File

@ -0,0 +1,19 @@
namespace Spectre.Console.Internal
{
/// <summary>
/// Represents something that can be rendered to a console.
/// </summary>
internal interface IRenderable
{
/// <summary>
/// Gets the length of the element.
/// </summary>
int Length { get; }
/// <summary>
/// Renders the element using the specified renderer.
/// </summary>
/// <param name="console">The renderer to use.</param>
void Render(IAnsiConsole console);
}
}

View File

@ -0,0 +1,36 @@
using System;
using Spectre.Console.Internal;
namespace Spectre.Console
{
internal static class ConsoleBuilder
{
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.Detect
? AnsiDetector.SupportsAnsi(true)
: settings.Ansi == AnsiSupport.Yes;
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
? ColorSystemDetector.Detect(supportsAnsi)
: (ColorSystem)settings.ColorSystem;
if (supportsAnsi)
{
return new AnsiConsoleRenderer(buffer, colorSystem)
{
Style = Styles.None,
};
}
return new FallbackConsoleRenderer(buffer, colorSystem);
}
}
}

View File

@ -0,0 +1,8 @@
namespace Spectre.Console.Internal
{
internal static class Constants
{
public const int DefaultBufferWidth = 80;
public const int DefaultBufferHeight = 9001;
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class ConsoleExtensions
{
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Foreground;
console.SetColor(color, foreground);
return new ColorScope(console, current, foreground);
}
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Style;
console.Style = style;
return new StyleScope(console, current);
}
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (foreground)
{
console.Foreground = color;
}
else
{
console.Background = color;
}
}
}
internal sealed class ColorScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Color _color;
private readonly bool _foreground;
public ColorScope(IAnsiConsole console, Color color, bool foreground)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_color = color;
_foreground = foreground;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~ColorScope()
{
throw new InvalidOperationException("Color scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_color, _foreground);
}
}
internal sealed class StyleScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Styles _style;
public StyleScope(IAnsiConsole console, Styles color)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = color;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.Style = _style;
}
}
}

View File

@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace Spectre.Console.Internal
{
internal static class TextWriterExtensions
{
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public static bool IsStandardOut(this TextWriter writer)
{
try
{
return writer == System.Console.Out;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.IO;
namespace Spectre.Console.Internal
{
internal sealed class AnsiConsoleRenderer : IAnsiConsole
{
private readonly TextWriter _out;
private readonly ColorSystem _system;
public AnsiConsoleCapabilities Capabilities { get; }
public Styles Style { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }
public int Width
{
get
{
if (_out.IsStandardOut())
{
return System.Console.BufferWidth;
}
return Constants.DefaultBufferWidth;
}
}
public int Height
{
get
{
if (_out.IsStandardOut())
{
return System.Console.BufferHeight;
}
return Constants.DefaultBufferHeight;
}
}
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system)
{
_out = @out ?? throw new ArgumentNullException(nameof(@out));
_system = system;
Capabilities = new AnsiConsoleCapabilities(true, system);
Foreground = Color.Default;
Background = Color.Default;
Style = Styles.None;
}
public void Reset(bool colors, bool styles)
{
if (colors)
{
Foreground = Color.Default;
Background = Color.Default;
}
if (styles)
{
Style = Styles.None;
}
}
public void Write(string text)
{
if (string.IsNullOrEmpty(text))
{
return;
}
_out.Write(AnsiBuilder.GetAnsi(
_system,
text,
Style,
Foreground,
Background));
}
public void WriteLine(string text)
{
if (text == null)
{
_out.WriteLine();
}
else
{
_out.WriteLine(
AnsiBuilder.GetAnsi(
_system,
text,
Style,
Foreground,
Background));
}
}
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.IO;
namespace Spectre.Console.Internal
{
internal sealed class FallbackConsoleRenderer : IAnsiConsole
{
private readonly ConsoleColor _defaultForeground;
private readonly ConsoleColor _defaultBackground;
private readonly TextWriter _out;
private readonly ColorSystem _system;
private ConsoleColor _foreground;
private ConsoleColor _background;
public AnsiConsoleCapabilities Capabilities { get; }
public int Width
{
get
{
if (_out.IsStandardOut())
{
return System.Console.BufferWidth;
}
return Constants.DefaultBufferWidth;
}
}
public int Height
{
get
{
if (_out.IsStandardOut())
{
return System.Console.BufferHeight;
}
return Constants.DefaultBufferHeight;
}
}
public Styles Style { get; set; }
public Color Foreground
{
get => _foreground;
set
{
_foreground = Color.ToConsoleColor(value);
if (_system != ColorSystem.NoColors && _out.IsStandardOut())
{
if ((int)_foreground == -1)
{
_foreground = _defaultForeground;
}
System.Console.ForegroundColor = _foreground;
}
}
}
public Color Background
{
get => _background;
set
{
_background = Color.ToConsoleColor(value);
if (_system != ColorSystem.NoColors && _out.IsStandardOut())
{
if ((int)_background == -1)
{
_background = _defaultBackground;
}
if (_system != ColorSystem.NoColors)
{
System.Console.BackgroundColor = _background;
}
}
}
}
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system)
{
_out = @out;
_system = system;
Capabilities = new AnsiConsoleCapabilities(false, _system);
if (_out.IsStandardOut())
{
_defaultForeground = System.Console.ForegroundColor;
_defaultBackground = System.Console.BackgroundColor;
}
else
{
_defaultForeground = ConsoleColor.Gray;
_defaultBackground = ConsoleColor.Black;
}
}
public void Write(string text)
{
_out.Write(text);
}
public void WriteLine(string text)
{
if (text == null)
{
_out.WriteLine();
}
else
{
_out.WriteLine(text);
}
}
}
}