Code/Dependencies/Pixie/Pixie.Terminal/Devices/AnsiStyleManager.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WasmBox.Pixie.Markup;
namespace WasmBox.Pixie.Terminal.Devices {
/// <summary>
/// A style manager implementation that uses ANSI control codes to
/// style output.
/// </summary>
public sealed class AnsiStyleManager : StyleManager {
/// <summary>
/// Creates an ANSI style manager from a text writer.
/// </summary>
/// <param name="writer">A text writer to which control sequences are written.</param>
// public AnsiStyleManager(TextWriter writer)
// : this(
// writer,
// ConsoleStyle.ToPixieColor(Console.ForegroundColor, Colors.White),
// ConsoleStyle.ToPixieColor(Console.BackgroundColor, Colors.Black))
// { }
/// <summary>
/// Creates an ANSI style manager from a text writer, a default foreground
/// and a default background color.
/// </summary>
/// <param name="writer">A text writer to which control sequences are written.</param>
/// <param name="defaultForegroundColor">The default foreground color.</param>
/// <param name="defaultBackgroundColor">The default background color.</param>
public AnsiStyleManager(
TextWriter writer,
Color defaultForegroundColor,
Color defaultBackgroundColor) {
this.Writer = writer;
this.defaultForegroundColor = defaultForegroundColor;
this.defaultBackgroundColor = defaultBackgroundColor;
this.styleStack = new Stack<AnsiStyle>();
this.styleStack.Push(
new AnsiStyle(
default( Color? ),
default( Color? ),
TextDecoration.None));
}
private Stack<AnsiStyle> styleStack;
private Color defaultForegroundColor;
private Color defaultBackgroundColor;
/// <summary>
/// Gets the writer to which ANSI control codes are written.
/// </summary>
/// <returns>A text writer.</returns>
public TextWriter Writer { get; private set; }
private AnsiStyle CurrentStyle => styleStack.Peek();
/// <inheritdoc/>
public override void PushForegroundColor(Color color) {
var curStyle = CurrentStyle;
PushStyle(
new AnsiStyle(
Over(color, curStyle.ForegroundColor, defaultForegroundColor),
curStyle.BackgroundColor,
curStyle.Decoration));
}
/// <inheritdoc/>
public override void PushBackgroundColor(Color color) {
var curStyle = CurrentStyle;
PushStyle(
new AnsiStyle(
curStyle.ForegroundColor,
Over(color, curStyle.BackgroundColor, defaultBackgroundColor),
curStyle.Decoration));
}
/// <inheritdoc/>
public override void PushDecoration(
TextDecoration decoration,
Func<TextDecoration, TextDecoration, TextDecoration> updateDecoration) {
var curStyle = CurrentStyle;
PushStyle(
new AnsiStyle(
curStyle.ForegroundColor,
curStyle.BackgroundColor,
updateDecoration(curStyle.Decoration, decoration)));
}
private void PushStyle(AnsiStyle style) {
style.Apply(Writer, CurrentStyle);
styleStack.Push(style);
}
/// <inheritdoc/>
public override void PopStyle() {
var popped = styleStack.Pop();
CurrentStyle.Apply(Writer, popped);
}
private static Color? Over(
Color top,
Color? bottom,
Color defaultBottom) {
if (top.Alpha == 0.0) {
return bottom;
}
else {
return top.Over(bottom.GetValueOrDefault(defaultBottom));
}
}
}
internal enum AnsiControlCode : byte {
Reset = 0,
Bold = 1,
Faint = 2,
Italic = 3,
Underline = 4,
BlinkSlow = 5,
BlinkFast = 6,
Strikethrough = 9,
ForegroundBlack = 30,
ForegroundRed = 31,
ForegroundGreen = 32,
ForegroundYellow = 33,
ForegroundBlue = 34,
ForegroundMagenta = 35,
ForegroundCyan = 36,
ForegroundWhite = 37,
BackgroundBlack = 40,
BackgroundRed = 41,
BackgroundGreen = 42,
BackgroundYellow = 43,
BackgroundBlue = 44,
BackgroundMagenta = 45,
BackgroundCyan = 46,
BackgroundWhite = 47,
}
internal sealed class AnsiStyle {
public AnsiStyle(
Color? foregroundColor,
Color? backgroundColor,
TextDecoration decoration) {
this.ForegroundColor = foregroundColor;
this.BackgroundColor = backgroundColor;
this.Decoration = decoration;
}
public Color? ForegroundColor { get; private set; }
public Color? BackgroundColor { get; private set; }
public TextDecoration Decoration { get; private set; }
private void WriteControlSequence(
TextWriter writer,
IEnumerable<AnsiControlCode> commands) {
writer.Write("\x1b[");
bool first = false;
foreach (var item in commands) {
if (first) {
first = false;
}
else {
writer.Write(';');
}
writer.Write((int)item);
}
writer.Write('m');
}
private void Apply(TextWriter writer) {
var commands = new List<AnsiControlCode>();
// Always reset first.
commands.Add(AnsiControlCode.Reset);
// Write the foreground color.
if (ForegroundColor.HasValue) {
bool isFaint;
commands.Add(ToForegroundColor(
ConsoleStyle.ToConsoleColor(ForegroundColor.Value),
out isFaint));
if (isFaint) {
commands.Add(AnsiControlCode.Faint);
}
}
// Write the background color.
if (BackgroundColor.HasValue) {
commands.Add(
ToBackgroundColor(
ConsoleStyle.ToConsoleColor(BackgroundColor.Value)));
}
// Apply decorations
if (HasDecoration(TextDecoration.Bold)) {
commands.Add(AnsiControlCode.Bold);
}
if (HasDecoration(TextDecoration.Italic)) {
commands.Add(AnsiControlCode.Italic);
}
if (HasDecoration(TextDecoration.Underline)) {
commands.Add(AnsiControlCode.Underline);
}
if (HasDecoration(TextDecoration.Strikethrough)) {
commands.Add(AnsiControlCode.Strikethrough);
}
WriteControlSequence(writer, commands);
}
private bool HasDecoration(TextDecoration decor) {
return (Decoration & decor) == decor;
}
/// <summary>
/// Applies this style, given a previous style.
/// </summary>
public void Apply(TextWriter writer, AnsiStyle style) {
if (Decoration != style.Decoration
|| !QuantizedColorEquals(ForegroundColor, style.ForegroundColor)
|| !QuantizedColorEquals(BackgroundColor, style.BackgroundColor)) {
Apply(writer);
}
}
private static AnsiControlCode ToForegroundColor(int color, out bool isFaint) {
switch (color) {
case 0:
isFaint = false;
return AnsiControlCode.ForegroundBlack;
case 9:
isFaint = false;
return AnsiControlCode.ForegroundBlue;
case 11:
isFaint = false;
return AnsiControlCode.ForegroundCyan;
case 10:
isFaint = false;
return AnsiControlCode.ForegroundGreen;
case 13:
isFaint = false;
return AnsiControlCode.ForegroundMagenta;
case 12:
isFaint = false;
return AnsiControlCode.ForegroundRed;
case 15:
isFaint = false;
return AnsiControlCode.ForegroundWhite;
case 14:
isFaint = false;
return AnsiControlCode.ForegroundYellow;
case 7:
isFaint = true;
return AnsiControlCode.ForegroundWhite;
case 1:
isFaint = true;
return AnsiControlCode.ForegroundBlue;
case 3:
isFaint = true;
return AnsiControlCode.ForegroundCyan;
case 8:
isFaint = true;
return AnsiControlCode.ForegroundBlack;
case 2:
isFaint = true;
return AnsiControlCode.ForegroundGreen;
case 5:
isFaint = true;
return AnsiControlCode.ForegroundMagenta;
case 4:
isFaint = true;
return AnsiControlCode.ForegroundRed;
case 6:
isFaint = true;
return AnsiControlCode.ForegroundYellow;
default:
throw new NotSupportedException("Unsupported color " + color);
}
}
private static AnsiControlCode ToBackgroundColor(int color) {
bool isFaint;
return ToForegroundColor(color, out isFaint) + 10;
}
private static bool QuantizedColorEquals(Color first, Color second) {
return ConsoleStyle.ToConsoleColor(first) == ConsoleStyle.ToConsoleColor(second);
}
private static bool QuantizedColorEquals(
Color? first,
Color? second ) {
if (first.HasValue) {
if (second.HasValue) {
return QuantizedColorEquals(first.Value, second.Value);
}
else {
return false;
}
}
else {
return !second.HasValue;
}
}
}
}