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,135 @@
using System;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static class AnsiConsole
{
private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() =>
{
return Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Detect,
ColorSystem = ColorSystemSupport.Detect,
Out = System.Console.Out,
});
});
/// <summary>
/// Gets the current renderer.
/// </summary>
public static IAnsiConsole Console => _console.Value;
/// <summary>
/// Gets the console's capabilities.
/// </summary>
public static AnsiConsoleCapabilities Capabilities => Console.Capabilities;
/// <summary>
/// Gets the buffer width of the console.
/// </summary>
public static int Width
{
get => Console.Width;
}
/// <summary>
/// Gets the buffer height of the console.
/// </summary>
public static int Height
{
get => Console.Height;
}
/// <summary>
/// Gets or sets the foreground color.
/// </summary>
public static Color Foreground
{
get => Console.Foreground;
set => Console.SetColor(value, true);
}
/// <summary>
/// Gets or sets the background color.
/// </summary>
public static Color Background
{
get => Console.Background;
set => Console.SetColor(value, false);
}
/// <summary>
/// Gets or sets the style.
/// </summary>
public static Styles Style
{
get => Console.Style;
set => Console.Style = value;
}
/// <summary>
/// Creates a new <see cref="IAnsiConsole"/> instance
/// from the provided settings.
/// </summary>
/// <param name="settings">The settings to use.</param>
/// <returns>An <see cref="IAnsiConsole"/> instance.</returns>
public static IAnsiConsole Create(AnsiConsoleSettings settings)
{
return ConsoleBuilder.Build(settings);
}
/// <summary>
/// Resets colors and styles to the default ones.
/// </summary>
public static void Reset()
{
Console.Reset();
}
/// <summary>
/// Resets the current style back to the default one.
/// </summary>
public static void ResetStyle()
{
Console.ResetStyle();
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// </summary>
public static void ResetColors()
{
Console.ResetColors();
}
/// <summary>
/// Writes the content to the console.
/// </summary>
/// <param name="content">The content to write.</param>
public static void Write(string content)
{
Console.Write(content);
}
/// <summary>
/// Writes an empty line to the console.
/// </summary>
public static void WriteLine()
{
Console.WriteLine();
}
/// <summary>
/// Writes a line to the console.
/// </summary>
/// <param name="content">The content to write.</param>
public static void WriteLine(string content)
{
Console.WriteLine(content);
}
}
}

View File

@ -0,0 +1,42 @@
namespace Spectre.Console
{
/// <summary>
/// Represents console capabilities.
/// </summary>
public sealed class AnsiConsoleCapabilities
{
/// <summary>
/// Gets a value indicating whether or not
/// the console supports Ansi.
/// </summary>
public bool SupportsAnsi { get; }
/// <summary>
/// Gets the color system.
/// </summary>
public ColorSystem ColorSystem { get; }
internal AnsiConsoleCapabilities(bool supportsAnsi, ColorSystem colorSystem)
{
SupportsAnsi = supportsAnsi;
ColorSystem = colorSystem;
}
/// <inheritdoc/>
public override string ToString()
{
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
var bits = ColorSystem switch
{
ColorSystem.NoColors => "1 bit",
ColorSystem.Legacy => "3 bits",
ColorSystem.Standard => "4 bits",
ColorSystem.EightBit => "8 bits",
ColorSystem.TrueColor => "24 bits",
_ => "?"
};
return $"ANSI={supportsAnsi}, Colors={ColorSystem} ({bits})";
}
}
}

View File

@ -0,0 +1,27 @@
using System.IO;
namespace Spectre.Console
{
/// <summary>
/// Settings used by <see cref="ConsoleBuilder"/>
/// when building a <see cref="IAnsiConsole"/>.
/// </summary>
public sealed class AnsiConsoleSettings
{
/// <summary>
/// Gets or sets a value indicating whether or
/// not ANSI escape sequences are supported.
/// </summary>
public AnsiSupport Ansi { get; set; }
/// <summary>
/// Gets or sets the color system to use.
/// </summary>
public ColorSystemSupport ColorSystem { get; set; }
/// <summary>
/// Gets or sets the out buffer.
/// </summary>
public TextWriter Out { get; set; }
}
}

View File

@ -0,0 +1,24 @@
namespace Spectre.Console
{
/// <summary>
/// Determines ANSI escape sequence support.
/// </summary>
public enum AnsiSupport
{
/// <summary>
/// ANSI escape sequence support should
/// be detected by the system.
/// </summary>
Detect = 0,
/// <summary>
/// ANSI escape sequences are supported.
/// </summary>
Yes = 1,
/// <summary>
/// ANSI escape sequences are not supported.
/// </summary>
No = 2,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,211 @@
using System;
using System.Diagnostics;
using System.Linq;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Represents a color.
/// </summary>
public partial struct Color : IEquatable<Color>
{
/// <summary>
/// Gets the default color.
/// </summary>
public static Color Default { get; }
static Color()
{
Default = new Color(0, "default", 0, 0, 0, true);
}
/// <summary>
/// Gets the red component.
/// </summary>
public byte R { get; }
/// <summary>
/// Gets the green component.
/// </summary>
public byte G { get; }
/// <summary>
/// Gets the blue component.
/// </summary>
public byte B { get; }
/// <summary>
/// Gets the name of the color, if any.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the number of the color, if any.
/// </summary>
internal byte? Number { get; }
/// <summary>
/// Gets a value indicating whether or not this is the default color.
/// </summary>
internal bool IsDefault { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="red">The red component.</param>
/// <param name="green">The green component.</param>
/// <param name="blue">The blue component.</param>
public Color(byte red, byte green, byte blue)
{
R = red;
G = green;
B = blue;
IsDefault = false;
Name = null;
Number = null;
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ R.GetHashCode();
hash = (hash * 16777619) ^ G.GetHashCode();
hash = (hash * 16777619) ^ B.GetHashCode();
return hash;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is Color color && Equals(color);
}
/// <inheritdoc/>
public bool Equals(Color other)
{
return Number == other.Number || (R == other.R && G == other.G && B == other.B);
}
/// <summary>
/// Checks if two <see cref="Color"/> instances are equal.
/// </summary>
/// <param name="left">The first color instance to compare.</param>
/// <param name="right">The second color instance to compare.</param>
/// <returns><c>true</c> if the two colors are equal, otherwise <c>false</c>.</returns>
public static bool operator ==(Color left, Color right)
{
return left.Equals(right);
}
/// <summary>
/// Checks if two <see cref="Color"/> instances are not equal.
/// </summary>
/// <param name="left">The first color instance to compare.</param>
/// <param name="right">The second color instance to compare.</param>
/// <returns><c>true</c> if the two colors are not equal, otherwise <c>false</c>.</returns>
public static bool operator !=(Color left, Color right)
{
return !(left == right);
}
/// <summary>
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
/// </summary>
/// <param name="color">The color to convert.</param>
public static implicit operator Color(ConsoleColor color)
{
return FromConsoleColor(color);
}
/// <summary>
/// Convers a color number into a <see cref="Color"/>.
/// </summary>
/// <param name="number">The color number.</param>
/// <returns>The color representing the specified color number.</returns>
public static Color FromColorNumber(int number)
{
if (number < 0 || number > 255)
{
throw new InvalidOperationException("Color number must be between 0 and 255");
}
return ColorPalette.EightBit.First(x => x.Number == number);
}
/// <summary>
/// Convers a <see cref="Color"/> to a <see cref="ConsoleColor"/>.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>A <see cref="ConsoleColor"/> representing the <see cref="Color"/>.</returns>
public static ConsoleColor ToConsoleColor(Color color)
{
if (color.IsDefault)
{
return (ConsoleColor)(-1);
}
if (color.Number == null || color.Number.Value >= 16)
{
color = ColorPalette.ExactOrClosest(ColorSystem.Standard, color);
}
// Should not happen, but this will make things easier if we mess things up...
Debug.Assert(color.Number >= 0 && color.Number < 16, "Color does not fall inside the standard palette range.");
return color.Number.Value switch
{
0 => ConsoleColor.Black,
1 => ConsoleColor.DarkRed,
2 => ConsoleColor.DarkGreen,
3 => ConsoleColor.DarkYellow,
4 => ConsoleColor.DarkBlue,
5 => ConsoleColor.DarkMagenta,
6 => ConsoleColor.DarkCyan,
7 => ConsoleColor.Gray,
8 => ConsoleColor.DarkGray,
9 => ConsoleColor.Red,
10 => ConsoleColor.Green,
11 => ConsoleColor.Yellow,
12 => ConsoleColor.Blue,
13 => ConsoleColor.Magenta,
14 => ConsoleColor.Cyan,
15 => ConsoleColor.White,
_ => throw new InvalidOperationException("Cannot convert color to console color."),
};
}
/// <summary>
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <returns>A <see cref="Color"/> representing the <see cref="ConsoleColor"/>.</returns>
public static Color FromConsoleColor(ConsoleColor color)
{
return color switch
{
ConsoleColor.Black => Black,
ConsoleColor.Blue => Blue,
ConsoleColor.Cyan => Aqua,
ConsoleColor.DarkBlue => Navy,
ConsoleColor.DarkCyan => Teal,
ConsoleColor.DarkGray => Grey,
ConsoleColor.DarkGreen => Green,
ConsoleColor.DarkMagenta => Purple,
ConsoleColor.DarkRed => Maroon,
ConsoleColor.DarkYellow => Olive,
ConsoleColor.Gray => Silver,
ConsoleColor.Green => Lime,
ConsoleColor.Magenta => Fuchsia,
ConsoleColor.Red => Red,
ConsoleColor.White => White,
ConsoleColor.Yellow => Yellow,
_ => Default,
};
}
}
}

View File

@ -0,0 +1,33 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a color system.
/// </summary>
public enum ColorSystem
{
/// <summary>
/// No colors.
/// </summary>
NoColors = 0,
/// <summary>
/// Legacy, 3-bit mode.
/// </summary>
Legacy = 1,
/// <summary>
/// Standard, 4-bit mode.
/// </summary>
Standard = 2,
/// <summary>
/// 8-bit mode.
/// </summary>
EightBit = 3,
/// <summary>
/// 24-bit mode.
/// </summary>
TrueColor = 4,
}
}

View File

@ -0,0 +1,38 @@
namespace Spectre.Console
{
/// <summary>
/// Determines what color system should be used.
/// </summary>
public enum ColorSystemSupport
{
/// <summary>
/// Try to detect the color system.
/// </summary>
Detect = -1,
/// <summary>
/// No colors.
/// </summary>
NoColors = 0,
/// <summary>
/// Legacy, 3-bit mode.
/// </summary>
Legacy = 1,
/// <summary>
/// Standard, 4-bit mode.
/// </summary>
Standard = 2,
/// <summary>
/// 8-bit mode.
/// </summary>
EightBit = 3,
/// <summary>
/// 24-bit mode.
/// </summary>
TrueColor = 4,
}
}

View File

@ -0,0 +1,83 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static class ConsoleExtensions
{
/// <summary>
/// Resets both colors and style for the console.
/// </summary>
/// <param name="console">The console to reset.</param>
public static void Reset(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.ResetColors();
console.ResetStyle();
}
/// <summary>
/// Resets the current style back to the default one.
/// </summary>
/// <param name="console">The console to reset the style for.</param>
public static void ResetStyle(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Style = Styles.None;
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// </summary>
/// <param name="console">The console to reset colors for.</param>
public static void ResetColors(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Foreground = Color.Default;
console.Background = Color.Default;
}
/// <summary>
/// Writes an empty line to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
public static void WriteLine(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(null);
}
/// <summary>
/// Writes a line to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="content">The content to write.</param>
public static void WriteLine(this IAnsiConsole console, string content)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(content);
}
}
}

View File

@ -0,0 +1,52 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a console.
/// </summary>
public interface IAnsiConsole
{
/// <summary>
/// Gets the console's capabilities.
/// </summary>
public AnsiConsoleCapabilities Capabilities { get; }
/// <summary>
/// Gets the buffer width of the console.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the buffer height of the console.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets or sets the current style.
/// </summary>
Styles Style { get; set; }
/// <summary>
/// Gets or sets the current foreground.
/// </summary>
Color Foreground { get; set; }
/// <summary>
/// Gets or sets the current background.
/// </summary>
Color Background { get; set; }
/// <summary>
/// Writes a string followed by a line terminator to the console.
/// </summary>
/// <param name="text">The string to write.</param>
void Write(string text);
/// <summary>
/// Writes a string followed by a line terminator to the console.
/// </summary>
/// <param name="text">
/// The string to write. If value is null, only the line terminator is written.
/// </param>
void WriteLine(string text);
}
}

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);
}
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<ItemGroup>
<Compile Update="Color.*.cs">
<DependentUpon>Color.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,75 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents a style.
/// </summary>
/// <remarks>
/// Support for different styles is up to the terminal.
/// </remarks>
[Flags]
public enum Styles
{
/// <summary>
/// No style.
/// </summary>
None = 0,
/// <summary>
/// Bold text.
/// Not supported in every environment.
/// </summary>
Bold = 1 << 0,
/// <summary>
/// Dim or faint text.
/// Not supported in every environment.
/// </summary>
Dim = 1 << 1,
/// <summary>
/// Italic text.
/// Not supported in every environment.
/// </summary>
Italic = 1 << 2,
/// <summary>
/// Underlined text.
/// Not supported in every environment.
/// </summary>
Underline = 1 << 3,
/// <summary>
/// Swaps the foreground and background colors.
/// Not supported in every environment.
/// </summary>
Invert = 1 << 4,
/// <summary>
/// Hides the text.
/// Not supported in every environment.
/// </summary>
Conceal = 1 << 5,
/// <summary>
/// Makes text blink.
/// Normally less than 150 blinks per minute.
/// Not supported in every environment.
/// </summary>
SlowBlink = 1 << 6,
/// <summary>
/// Makes text blink.
/// Normally more than 150 blinks per minute.
/// Not supported in every environment.
/// </summary>
RapidBlink = 1 << 7,
/// <summary>
/// Shows text with a horizontal line through the center.
/// Not supported in every environment.
/// </summary>
Strikethrough = 1 << 8,
}
}