PowerShell/src/Microsoft.PowerShell.MarkdownRender/VT100EscapeSequences.cs

479 lines
16 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Management.Automation;
namespace Microsoft.PowerShell.MarkdownRender
{
/// <summary>
/// Enum to name all the properties of PSMarkdownOptionInfo.
/// </summary>
public enum MarkdownOptionInfoProperty
{
/// <summary>
/// Property name Header1.
/// </summary>
Header1,
/// <summary>
/// Property name Header2.
/// </summary>
Header2,
/// <summary>
/// Property name Header3.
/// </summary>
Header3,
/// <summary>
/// Property name Header4.
/// </summary>
Header4,
/// <summary>
/// Property name Header5.
/// </summary>
Header5,
/// <summary>
/// Property name Header6.
/// </summary>
Header6,
/// <summary>
/// Property name Code.
/// </summary>
Code,
/// <summary>
/// Property name Link.
/// </summary>
Link,
/// <summary>
/// Property name Image.
/// </summary>
Image,
/// <summary>
/// Property name EmphasisBold.
/// </summary>
EmphasisBold,
/// <summary>
/// Property name EmphasisItalics.
/// </summary>
EmphasisItalics
}
/// <summary>
/// Class to represent color preference options for various Markdown elements.
/// </summary>
public sealed class PSMarkdownOptionInfo
{
private const char Esc = (char)0x1b;
private const string EndSequence = "[0m";
/// <summary>
/// Gets or sets current VT100 escape sequence for header 1.
/// </summary>
public string Header1 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for header 2.
/// </summary>
public string Header2 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for header 3.
/// </summary>
public string Header3 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for header 4.
/// </summary>
public string Header4 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for header 5.
/// </summary>
public string Header5 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for header 6.
/// </summary>
public string Header6 { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for code inline and code blocks.
/// </summary>
public string Code { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for links.
/// </summary>
public string Link { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for images.
/// </summary>
public string Image { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for bold text.
/// </summary>
public string EmphasisBold { get; set; }
/// <summary>
/// Gets or sets current VT100 escape sequence for italics text.
/// </summary>
public string EmphasisItalics { get; set; }
/// <summary>
/// Gets or sets a value indicating whether VT100 escape sequences should be added. Default it true.
/// </summary>
public bool EnableVT100Encoding { get; set; }
/// <summary>
/// Get the property as an rendered escape sequence.
/// This is used by formatting system for displaying.
/// </summary>
/// <param name="propertyName">Name of the property to get as escape sequence.</param>
/// <returns>Specified property name as escape sequence.</returns>
public string AsEscapeSequence(MarkdownOptionInfoProperty propertyName)
{
switch (propertyName)
{
case MarkdownOptionInfoProperty.Header1:
return string.Concat(Esc, Header1, Header1, Esc, EndSequence);
case MarkdownOptionInfoProperty.Header2:
return string.Concat(Esc, Header2, Header2, Esc, EndSequence);
case MarkdownOptionInfoProperty.Header3:
return string.Concat(Esc, Header3, Header3, Esc, EndSequence);
case MarkdownOptionInfoProperty.Header4:
return string.Concat(Esc, Header4, Header4, Esc, EndSequence);
case MarkdownOptionInfoProperty.Header5:
return string.Concat(Esc, Header5, Header5, Esc, EndSequence);
case MarkdownOptionInfoProperty.Header6:
return string.Concat(Esc, Header6, Header6, Esc, EndSequence);
case MarkdownOptionInfoProperty.Code:
return string.Concat(Esc, Code, Code, Esc, EndSequence);
case MarkdownOptionInfoProperty.Link:
return string.Concat(Esc, Link, Link, Esc, EndSequence);
case MarkdownOptionInfoProperty.Image:
return string.Concat(Esc, Image, Image, Esc, EndSequence);
case MarkdownOptionInfoProperty.EmphasisBold:
return string.Concat(Esc, EmphasisBold, EmphasisBold, Esc, EndSequence);
case MarkdownOptionInfoProperty.EmphasisItalics:
return string.Concat(Esc, EmphasisItalics, EmphasisItalics, Esc, EndSequence);
default:
break;
}
return null;
}
/// <summary>
/// Initializes a new instance of the <see cref="PSMarkdownOptionInfo"/> class and sets dark as the default theme.
/// </summary>
public PSMarkdownOptionInfo()
{
SetDarkTheme();
EnableVT100Encoding = true;
}
private const string Header1Dark = "[7m";
private const string Header2Dark = "[4;93m";
private const string Header3Dark = "[4;94m";
private const string Header4Dark = "[4;95m";
private const string Header5Dark = "[4;96m";
private const string Header6Dark = "[4;97m";
private const string CodeDark = "[48;2;155;155;155;38;2;30;30;30m";
private const string CodeMacOS = "[107;95m";
private const string LinkDark = "[4;38;5;117m";
private const string ImageDark = "[33m";
private const string EmphasisBoldDark = "[1m";
private const string EmphasisItalicsDark = "[36m";
private const string Header1Light = "[7m";
private const string Header2Light = "[4;33m";
private const string Header3Light = "[4;34m";
private const string Header4Light = "[4;35m";
private const string Header5Light = "[4;36m";
private const string Header6Light = "[4;30m";
private const string CodeLight = "[48;2;155;155;155;38;2;30;30;30m";
private const string LinkLight = "[4;38;5;117m";
private const string ImageLight = "[33m";
private const string EmphasisBoldLight = "[1m";
private const string EmphasisItalicsLight = "[36m";
/// <summary>
/// Set all preference for dark theme.
/// </summary>
public void SetDarkTheme()
{
Header1 = Header1Dark;
Header2 = Header2Dark;
Header3 = Header3Dark;
Header4 = Header4Dark;
Header5 = Header5Dark;
Header6 = Header6Dark;
Link = LinkDark;
Image = ImageDark;
EmphasisBold = EmphasisBoldDark;
EmphasisItalics = EmphasisItalicsDark;
SetCodeColor(isDarkTheme: true);
}
/// <summary>
/// Set all preference for light theme.
/// </summary>
public void SetLightTheme()
{
Header1 = Header1Light;
Header2 = Header2Light;
Header3 = Header3Light;
Header4 = Header4Light;
Header5 = Header5Light;
Header6 = Header6Light;
Link = LinkLight;
Image = ImageLight;
EmphasisBold = EmphasisBoldLight;
EmphasisItalics = EmphasisItalicsLight;
SetCodeColor(isDarkTheme: false);
}
private void SetCodeColor(bool isDarkTheme)
{
// MacOS terminal app does not support extended colors for VT100, so we special case for it.
Code = Platform.IsMacOS ? CodeMacOS : isDarkTheme ? CodeDark : CodeLight;
}
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
public class VT100EscapeSequences
{
private const char Esc = (char)0x1B;
private readonly string endSequence = Esc + "[0m";
// For code blocks, [500@ make sure that the whole line has background color.
private const string LongBackgroundCodeBlock = "[500@";
private readonly PSMarkdownOptionInfo options;
/// <summary>
/// Initializes a new instance of the <see cref="VT100EscapeSequences"/> class.
/// </summary>
/// <param name="optionInfo">PSMarkdownOptionInfo object to initialize with.</param>
public VT100EscapeSequences(PSMarkdownOptionInfo optionInfo)
{
if (optionInfo == null)
{
throw new ArgumentNullException(nameof(optionInfo));
}
options = optionInfo;
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 1 string.</returns>
public string FormatHeader1(string headerText)
{
return FormatHeader(headerText, options.Header1);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 2 string.</returns>
public string FormatHeader2(string headerText)
{
return FormatHeader(headerText, options.Header2);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 3 string.</returns>
public string FormatHeader3(string headerText)
{
return FormatHeader(headerText, options.Header3);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 4 string.</returns>
public string FormatHeader4(string headerText)
{
return FormatHeader(headerText, options.Header4);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 5 string.</returns>
public string FormatHeader5(string headerText)
{
return FormatHeader(headerText, options.Header5);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="headerText">Text of the header to format.</param>
/// <returns>Formatted Header 6 string.</returns>
public string FormatHeader6(string headerText)
{
return FormatHeader(headerText, options.Header6);
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="codeText">Text of the code block to format.</param>
/// <param name="isInline">True if it is a inline code block, false otherwise.</param>
/// <returns>Formatted code block string.</returns>
public string FormatCode(string codeText, bool isInline)
{
bool isVT100Enabled = options.EnableVT100Encoding;
if (isInline)
{
if (isVT100Enabled)
{
return string.Concat(Esc, options.Code, codeText, endSequence);
}
else
{
return codeText;
}
}
else
{
if (isVT100Enabled)
{
return string.Concat(Esc, options.Code, codeText, Esc, LongBackgroundCodeBlock, endSequence);
}
else
{
return codeText;
}
}
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="linkText">Text of the link to format.</param>
/// <param name="url">URL of the link.</param>
/// <param name="hideUrl">True url should be hidden, false otherwise. Default is true.</param>
/// <returns>Formatted link string.</returns>
public string FormatLink(string linkText, string url, bool hideUrl = true)
{
bool isVT100Enabled = options.EnableVT100Encoding;
if (hideUrl)
{
if (isVT100Enabled)
{
return string.Concat(Esc, options.Link, "\"", linkText, "\"", endSequence);
}
else
{
return string.Concat("\"", linkText, "\"");
}
}
else
{
if (isVT100Enabled)
{
return string.Concat("\"", linkText, "\" (", Esc, options.Link, url, endSequence, ")");
}
else
{
return string.Concat("\"", linkText, "\" (", url, ")");
}
}
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="emphasisText">Text to format as emphasis.</param>
/// <param name="isBold">True if it is to be formatted as bold, false to format it as italics.</param>
/// <returns>Formatted emphasis string.</returns>
public string FormatEmphasis(string emphasisText, bool isBold)
{
var sequence = isBold ? options.EmphasisBold : options.EmphasisItalics;
if (options.EnableVT100Encoding)
{
return string.Concat(Esc, sequence, emphasisText, endSequence);
}
else
{
return emphasisText;
}
}
/// <summary>
/// Class to represent default VT100 escape sequences.
/// </summary>
/// <param name="altText">Text of the image to format.</param>
/// <returns>Formatted image string.</returns>
public string FormatImage(string altText)
{
var text = altText;
if (string.IsNullOrEmpty(altText))
{
text = "Image";
}
if (options.EnableVT100Encoding)
{
return string.Concat(Esc, options.Image, "[", text, "]", endSequence);
}
else
{
return string.Concat("[", text, "]");
}
}
private string FormatHeader(string headerText, string headerEscapeSequence)
{
if (options.EnableVT100Encoding)
{
return string.Concat(Esc, headerEscapeSequence, headerText, endSequence);
}
else
{
return headerText;
}
}
}
}