diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 5c319347a8..7485306c14 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -696,6 +696,7 @@ namespace Microsoft.PowerShell } TextWriter writer = Console.IsOutputRedirected ? Console.Out : _parent.ConsoleTextWriter; + value = Utils.GetOutputString(value, isHost: true, SupportsVirtualTerminal, Console.IsOutputRedirected); if (_parent.IsRunningAsync) { @@ -1179,11 +1180,17 @@ namespace Microsoft.PowerShell } else { - // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here... - WriteLine( - DebugForegroundColor, - DebugBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message)); + if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Debug) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message)); + } + else + { + WriteLine( + DebugForegroundColor, + DebugBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message)); + } } } @@ -1234,10 +1241,17 @@ namespace Microsoft.PowerShell } else { - WriteLine( - VerboseForegroundColor, - VerboseBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message)); + if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Verbose) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message)); + } + else + { + WriteLine( + VerboseForegroundColor, + VerboseBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message)); + } } } @@ -1271,10 +1285,17 @@ namespace Microsoft.PowerShell } else { - WriteLine( - WarningForegroundColor, - WarningBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message)); + if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + WriteLine(Utils.GetFormatStyleString(Utils.FormatStyle.Warning) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message)); + } + else + { + WriteLine( + WarningForegroundColor, + WarningBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message)); + } } } @@ -1334,9 +1355,20 @@ namespace Microsoft.PowerShell else { if (writer == _parent.ConsoleTextWriter) - WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + { + if (SupportsVirtualTerminal && ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + WriteLine(value); + } + else + { + WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + } + } else + { Console.Error.WriteLine(value); + } } } diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 0400d3488b..dbb8d3a208 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -263,6 +263,22 @@ namespace System.Management.Automation.Runspaces yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.ByteCollection", ViewsOf_Microsoft_PowerShell_Commands_ByteCollection()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle", + ViewsOf_System_Management_Automation_PSStyle()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+FormattingData", + ViewsOf_System_Management_Automation_PSStyleFormattingData()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+ForegroundColor", + ViewsOf_System_Management_Automation_PSStyleForegroundColor()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+BackgroundColor", + ViewsOf_System_Management_Automation_PSStyleBackgroundColor()); } private static IEnumerable ViewsOf_System_RuntimeType() @@ -1317,7 +1333,7 @@ namespace System.Management.Automation.Runspaces $err.CategoryInfo.GetMessage() } elseif (! $err.ErrorDetails -or ! $err.ErrorDetails.Message) { - $err.Exception.Message + $posmsg + $newline + $err.Exception.Message + $posmsg } else { $err.ErrorDetails.Message + $posmsg } @@ -2004,5 +2020,131 @@ namespace System.Management.Automation.Runspaces .GroupByProperty("Label") .EndTable()); } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyle() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Reset)$($_.Reset.Replace(""""`e"""",'`e'))""", label: "Reset") + .AddItemScriptBlock(@"""$($_.BlinkOff)$($_.BlinkOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "BlinkOff") + .AddItemScriptBlock(@"""$($_.Blink)$($_.Blink.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Blink") + .AddItemScriptBlock(@"""$($_.BoldOff)$($_.BoldOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "BoldOff") + .AddItemScriptBlock(@"""$($_.Bold)$($_.Bold.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Bold") + .AddItemScriptBlock(@"""$($_.Hidden)$($_.Hidden.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Hidden") + .AddItemScriptBlock(@"""$($_.HiddenOff)$($_.HiddenOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "HiddenOff") + .AddItemScriptBlock(@"""$($_.Reverse)$($_.Reverse.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Reverse") + .AddItemScriptBlock(@"""$($_.ReverseOff)$($_.ReverseOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "ReverseOff") + .AddItemScriptBlock(@"""$($_.ItalicOff)$($_.ItalicOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "ItalicOff") + .AddItemScriptBlock(@"""$($_.Italic)$($_.Italic.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Italic") + .AddItemScriptBlock(@"""$($_.UnderlineOff)$($_.UnderlineOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "UnderlineOff") + .AddItemScriptBlock(@"""$($_.Underline)$($_.Underline.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Underline") + .AddItemProperty(@"OutputRendering") + .AddItemScriptBlock(@"""$($_.Formatting.FormatAccent)$($_.Formatting.FormatAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FormatAccent") + .AddItemScriptBlock(@"""$($_.Formatting.ErrorAccent)$($_.Formatting.ErrorAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.ErrorAccent") + .AddItemScriptBlock(@"""$($_.Formatting.Error)$($_.Formatting.Error.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Error") + .AddItemScriptBlock(@"""$($_.Formatting.Warning)$($_.Formatting.Warning.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Warning") + .AddItemScriptBlock(@"""$($_.Formatting.Verbose)$($_.Formatting.Verbose.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Verbose") + .AddItemScriptBlock(@"""$($_.Formatting.Debug)$($_.Formatting.Debug.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Debug") + .AddItemScriptBlock(@"""$($_.Foreground.Black)$($_.Foreground.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Black") + .AddItemScriptBlock(@"""$($_.Foreground.White)$($_.Foreground.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.White") + .AddItemScriptBlock(@"""$($_.Foreground.DarkGray)$($_.Foreground.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.DarkGray") + .AddItemScriptBlock(@"""$($_.Foreground.LightGray)$($_.Foreground.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightGray") + .AddItemScriptBlock(@"""$($_.Foreground.Red)$($_.Foreground.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Red") + .AddItemScriptBlock(@"""$($_.Foreground.LightRed)$($_.Foreground.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightRed") + .AddItemScriptBlock(@"""$($_.Foreground.Magenta)$($_.Foreground.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Magenta") + .AddItemScriptBlock(@"""$($_.Foreground.LightMagenta)$($_.Foreground.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightMagenta") + .AddItemScriptBlock(@"""$($_.Foreground.Blue)$($_.Foreground.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Blue") + .AddItemScriptBlock(@"""$($_.Foreground.LightBlue)$($_.Foreground.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightBlue") + .AddItemScriptBlock(@"""$($_.Foreground.Cyan)$($_.Foreground.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Cyan") + .AddItemScriptBlock(@"""$($_.Foreground.LightCyan)$($_.Foreground.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightCyan") + .AddItemScriptBlock(@"""$($_.Foreground.Green)$($_.Foreground.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Green") + .AddItemScriptBlock(@"""$($_.Foreground.LightGreen)$($_.Foreground.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightGreen") + .AddItemScriptBlock(@"""$($_.Foreground.Yellow)$($_.Foreground.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Yellow") + .AddItemScriptBlock(@"""$($_.Foreground.LightYellow)$($_.Foreground.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.LightYellow") + .AddItemScriptBlock(@"""$($_.Background.Black)$($_.Background.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Black") + .AddItemScriptBlock(@"""$($_.Background.White)$($_.Background.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.White") + .AddItemScriptBlock(@"""$($_.Background.DarkGray)$($_.Background.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.DarkGray") + .AddItemScriptBlock(@"""$($_.Background.LightGray)$($_.Background.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightGray") + .AddItemScriptBlock(@"""$($_.Background.Red)$($_.Background.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Red") + .AddItemScriptBlock(@"""$($_.Background.LightRed)$($_.Background.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightRed") + .AddItemScriptBlock(@"""$($_.Background.Magenta)$($_.Background.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Magenta") + .AddItemScriptBlock(@"""$($_.Background.LightMagenta)$($_.Background.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightMagenta") + .AddItemScriptBlock(@"""$($_.Background.Blue)$($_.Background.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Blue") + .AddItemScriptBlock(@"""$($_.Background.LightBlue)$($_.Background.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightBlue") + .AddItemScriptBlock(@"""$($_.Background.Cyan)$($_.Background.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Cyan") + .AddItemScriptBlock(@"""$($_.Background.LightCyan)$($_.Background.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightCyan") + .AddItemScriptBlock(@"""$($_.Background.Green)$($_.Background.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Green") + .AddItemScriptBlock(@"""$($_.Background.LightGreen)$($_.Background.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightGreen") + .AddItemScriptBlock(@"""$($_.Background.Yellow)$($_.Background.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.Yellow") + .AddItemScriptBlock(@"""$($_.Background.LightYellow)$($_.Background.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.LightYellow") + .EndEntry() + .EndList()); + } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleFormattingData() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+FormattingData", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.FormatAccent)$($_.FormatAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FormatAccent") + .AddItemScriptBlock(@"""$($_.ErrorAccent)$($_.ErrorAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "ErrorAccent") + .AddItemScriptBlock(@"""$($_.Error)$($_.Error.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Error") + .AddItemScriptBlock(@"""$($_.Warning)$($_.Warning.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Warning") + .AddItemScriptBlock(@"""$($_.Verbose)$($_.Verbose.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Verbose") + .AddItemScriptBlock(@"""$($_.Debug)$($_.Debug.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Debug") + .EndEntry() + .EndList()); + } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleForegroundColor() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Black)$($_.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Black") + .AddItemScriptBlock(@"""$($_.White)$($_.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "White") + .AddItemScriptBlock(@"""$($_.DarkGray)$($_.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "DarkGray") + .AddItemScriptBlock(@"""$($_.LightGray)$($_.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGray") + .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") + .AddItemScriptBlock(@"""$($_.LightRed)$($_.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightRed") + .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") + .AddItemScriptBlock(@"""$($_.LightMagenta)$($_.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightMagenta") + .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") + .AddItemScriptBlock(@"""$($_.LightBlue)$($_.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightBlue") + .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") + .AddItemScriptBlock(@"""$($_.LightCyan)$($_.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightCyan") + .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") + .AddItemScriptBlock(@"""$($_.LightGreen)$($_.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGreen") + .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") + .AddItemScriptBlock(@"""$($_.LightYellow)$($_.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightYellow") + .EndEntry() + .EndList()); + } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleBackgroundColor() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Black)$($_.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Black") + .AddItemScriptBlock(@"""$($_.White)$($_.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "White") + .AddItemScriptBlock(@"""$($_.DarkGray)$($_.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "DarkGray") + .AddItemScriptBlock(@"""$($_.LightGray)$($_.LightGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGray") + .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") + .AddItemScriptBlock(@"""$($_.LightRed)$($_.LightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightRed") + .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") + .AddItemScriptBlock(@"""$($_.LightMagenta)$($_.LightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightMagenta") + .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") + .AddItemScriptBlock(@"""$($_.LightBlue)$($_.LightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightBlue") + .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") + .AddItemScriptBlock(@"""$($_.LightCyan)$($_.LightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightCyan") + .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") + .AddItemScriptBlock(@"""$($_.LightGreen)$($_.LightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightGreen") + .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") + .AddItemScriptBlock(@"""$($_.LightYellow)$($_.LightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "LightYellow") + .EndEntry() + .EndList()); + } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs index 83e39c4dd5..32d4565da5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Text; // interfaces for host interaction @@ -411,6 +412,9 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal override void WriteLine(string s) { CheckStopProcessing(); + + s = Utils.GetOutputString(s, isHost: false); + if (_suppressNewline) { _writer.Write(s); diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs new file mode 100644 index 0000000000..f27a5b89da --- /dev/null +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -0,0 +1,404 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace System.Management.Automation +{ + #region OutputRendering + /// + /// Defines the options for output rendering. + /// + public enum OutputRendering + { + /// Automatic by PowerShell. + Automatic = 0, + + /// Render as plaintext. + PlainText = 1, + + /// Render as ANSI. + Ansi = 2, + + /// Render ANSI only to host. + Host = 3, + } + #endregion OutputRendering + + #region PSStyle + /// + /// Contains configuration for how PowerShell renders text. + /// + public class PSStyle + { + /// + /// Contains foreground colors. + /// + public class ForegroundColor + { + /// + /// Gets the color black. + /// + public string Black { get; } = "\x1b[30m"; + + /// + /// Gets the color blue. + /// + public string Blue { get; } = "\x1b[34m"; + + /// + /// Gets the color cyan. + /// + public string Cyan { get; } = "\x1b[36m"; + + /// + /// Gets the color dark gray. + /// + public string DarkGray { get; } = "\x1b[90m"; + + /// + /// Gets the color green. + /// + public string Green { get; } = "\x1b[32m"; + + /// + /// Gets the color light blue. + /// + public string LightBlue { get; } = "\x1b[94m"; + + /// + /// Gets the color light cyan. + /// + public string LightCyan { get; } = "\x1b[96m"; + + /// + /// Gets the color light gray. + /// + public string LightGray { get; } = "\x1b[97m"; + + /// + /// Gets the color light green. + /// + public string LightGreen { get; } = "\x1b[92m"; + + /// + /// Gets the color light magenta. + /// + public string LightMagenta { get; } = "\x1b[95m"; + + /// + /// Gets the color light red. + /// + public string LightRed { get; } = "\x1b[91m"; + + /// + /// Gets the color light yellow. + /// + public string LightYellow { get; } = "\x1b[93m"; + + /// + /// Gets the color magenta. + /// + public string Magenta { get; } = "\x1b[35m"; + + /// + /// Gets the color read. + /// + public string Red { get; } = "\x1b[31m"; + + /// + /// Gets the color white. + /// + public string White { get; } = "\x1b[37m"; + + /// + /// Gets the color yellow. + /// + public string Yellow { get; } = "\x1b[33m"; + + /// + /// Set as RGB (Red, Green, Blue). + /// + /// Byte value representing red. + /// Byte value representing green. + /// Byte value representing blue. + /// String representing ANSI code for RGB value. + public string FromRgb(byte red, byte green, byte blue) + { + return $"\x1b[38;2;{red};{green};{blue}m"; + } + + /// + /// The color set as RGB as a single number. + /// + /// RGB value specified as an integer. + /// String representing ANSI code for RGB value. + public string FromRgb(int rgb) + { + byte red, green, blue; + blue = (byte)(rgb & 0xFF); + rgb >>= 8; + green = (byte)(rgb & 0xFF); + rgb >>= 8; + red = (byte)(rgb & 0xFF); + + return FromRgb(red, green, blue); + } + } + + /// + /// Contains background colors. + /// + public class BackgroundColor + { + /// + /// Gets the color black. + /// + public string Black { get; } = "\x1b[40m"; + + /// + /// Gets the color blue. + /// + public string Blue { get; } = "\x1b[44m"; + + /// + /// Gets the color cyan. + /// + public string Cyan { get; } = "\x1b[46m"; + + /// + /// Gets the color dark gray. + /// + public string DarkGray { get; } = "\x1b[100m"; + + /// + /// Gets the color green. + /// + public string Green { get; } = "\x1b[42m"; + + /// + /// Gets the color light blue. + /// + public string LightBlue { get; } = "\x1b[104m"; + + /// + /// Gets the color light cyan. + /// + public string LightCyan { get; } = "\x1b[106m"; + + /// + /// Gets the color light gray. + /// + public string LightGray { get; } = "\x1b[107m"; + + /// + /// Gets the color light green. + /// + public string LightGreen { get; } = "\x1b[102m"; + + /// + /// Gets the color light magenta. + /// + public string LightMagenta { get; } = "\x1b[105m"; + + /// + /// Gets the color light red. + /// + public string LightRed { get; } = "\x1b[101m"; + + /// + /// Gets the color light yellow. + /// + public string LightYellow { get; } = "\x1b[103m"; + + /// + /// Gets the color magenta. + /// + public string Magenta { get; } = "\x1b[45m"; + + /// + /// Gets the color read. + /// + public string Red { get; } = "\x1b[41m"; + + /// + /// Gets the color white. + /// + public string White { get; } = "\x1b[47m"; + + /// + /// Gets the color yellow. + /// + public string Yellow { get; } = "\x1b[43m"; + + /// + /// The color set as RGB (Red, Green, Blue). + /// + /// Byte value representing red. + /// Byte value representing green. + /// Byte value representing blue. + /// String representing ANSI code for RGB value. + public string FromRgb(byte red, byte green, byte blue) + { + return $"\x1b[48;2;{red};{green};{blue}m"; + } + + /// + /// The color set as RGB as a single number. + /// + /// RGB value specified as an integer. + /// String representing ANSI code for RGB value. + public string FromRgb(int rgb) + { + byte red, green, blue; + blue = (byte)(rgb & 0xFF); + rgb >>= 8; + green = (byte)(rgb & 0xFF); + rgb >>= 8; + red = (byte)(rgb & 0xFF); + + return FromRgb(red, green, blue); + } + } + + /// + /// Contains formatting styles for steams and objects. + /// + public class FormattingData + { + /// + /// Gets or sets the accent style for formatting. + /// + public string FormatAccent { get; set; } = "\x1b[32;1m"; + + /// + /// Gets or sets the accent style for errors. + /// + public string ErrorAccent { get; set; } = "\x1b[36;1m"; + + /// + /// Gets or sets the style for error messages. + /// + public string Error { get; set; } = "\x1b[31;1m"; + + /// + /// Gets or sets the style for warning messages. + /// + public string Warning { get; set; } = "\x1b[33;1m"; + + /// + /// Gets or sets the style for verbose messages. + /// + public string Verbose { get; set; } = "\x1b[33;1m"; + + /// + /// Gets or sets the style for debug messages. + /// + public string Debug { get; set; } = "\x1b[33;1m"; + } + + /// + /// Gets or sets the rendering mode for output. + /// + public OutputRendering OutputRendering { get; set; } = OutputRendering.Automatic; + + /// + /// Gets value to turn off all attributes. + /// + public string Reset { get; } = "\x1b[0m"; + + /// + /// Gets value to turn off blink. + /// + public string BlinkOff { get; } = "\x1b[5m"; + + /// + /// Gets value to turn on blink. + /// + public string Blink { get; } = "\x1b[25m"; + + /// + /// Gets value to turn off bold. + /// + public string BoldOff { get; } = "\x1b[22m"; + + /// + /// Gets value to turn on blink. + /// + public string Bold { get; } = "\x1b[1m"; + + /// + /// Gets value to turn on hidden. + /// + public string Hidden { get; } = "\x1b[8m"; + + /// + /// Gets value to turn off hidden. + /// + public string HiddenOff { get; } = "\x1b[28m"; + + /// + /// Gets value to turn on reverse. + /// + public string Reverse { get; } = "\x1b[7m"; + + /// + /// Gets value to turn off reverse. + /// + public string ReverseOff { get; } = "\x1b[27m"; + + /// + /// Gets value to turn off standout. + /// + public string ItalicOff { get; } = "\x1b[23m"; + + /// + /// Gets value to turn on standout. + /// + public string Italic { get; } = "\x1b[3m"; + + /// + /// Gets value to turn off underlined. + /// + public string UnderlineOff { get; } = "\x1b[24m"; + + /// + /// Gets value to turn on underlined. + /// + public string Underline { get; } = "\x1b[4m"; + + /// + /// Gets the formatting rendering settings. + /// + public FormattingData Formatting { get; } + + /// + /// Gets foreground colors. + /// + public ForegroundColor Foreground { get; } + + /// + /// Gets background colors. + /// + public BackgroundColor Background { get; } + + private static readonly PSStyle s_psstyle = new PSStyle(); + + private PSStyle() + { + Formatting = new FormattingData(); + Foreground = new ForegroundColor(); + Background = new BackgroundColor(); + } + + /// + /// Gets singleton instance. + /// + public static PSStyle Instance + { + get + { + return s_psstyle; + } + } + } + #endregion PSStyle +} diff --git a/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs b/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs new file mode 100644 index 0000000000..3c1041f9c4 --- /dev/null +++ b/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Text.RegularExpressions; + +namespace System.Management.Automation.Internal +{ + /// + /// Extensions to String type to calculate and render decorated content. + /// + public class StringDecorated + { + private readonly bool _isDecorated; + private readonly string _text; + private string? _plaintextcontent; + + private string PlainText + { + get + { + if (_plaintextcontent == null) + { + _plaintextcontent = ValueStringDecorated.AnsiRegex.Replace(_text, string.Empty); + } + + return _plaintextcontent; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The input string. + public StringDecorated(string text) + { + _text = text; + _isDecorated = text.Contains(ValueStringDecorated.ESC); + } + + /// + /// Gets a value indicating whether the string contains decoration. + /// + /// Boolean if the string contains decoration. + public bool IsDecorated => _isDecorated; + + /// + /// Gets the length of content sans escape sequences. + /// + /// Length of content sans escape sequences. + public int ContentLength => PlainText.Length; + + /// + /// Render the decorarted string using automatic output rendering. + /// + /// Rendered string based on automatic output rendering. + public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text; + + /// + /// Return string representation of content depending on output rendering mode. + /// + /// Specify how to render the text content. + /// Rendered string based on outputRendering. + public string ToString(OutputRendering outputRendering) + { + if (!_isDecorated) + { + return _text; + } + + if (outputRendering == OutputRendering.Automatic) + { + outputRendering = OutputRendering.Ansi; + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + outputRendering = OutputRendering.PlainText; + } + } + + if (outputRendering == OutputRendering.PlainText) + { + return PlainText; + } + else + { + return _text; + } + } + } + + internal struct ValueStringDecorated + { + internal const char ESC = '\x1b'; + private readonly bool _isDecorated; + private readonly string _text; + private string? _plaintextcontent; + + private string PlainText + { + get + { + if (_plaintextcontent == null) + { + _plaintextcontent = AnsiRegex.Replace(_text, string.Empty); + } + + return _plaintextcontent; + } + } + + // replace regex with .NET 6 API once available + internal static readonly Regex AnsiRegex = new Regex(@"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", RegexOptions.Compiled); + + /// + /// Initializes a new instance of the struct. + /// + /// The input string. + public ValueStringDecorated(string text) + { + _text = text; + _isDecorated = text.Contains(ESC); + _plaintextcontent = null; + } + + /// + /// Gets a value indicating whether the string contains decoration. + /// + /// Boolean if the string contains decoration. + public bool IsDecorated => _isDecorated; + + /// + /// Gets the length of content sans escape sequences. + /// + /// Length of content sans escape sequences. + public int ContentLength => PlainText.Length; + + /// + /// Render the decorarted string using automatic output rendering. + /// + /// Rendered string based on automatic output rendering. + public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text; + + /// + /// Return string representation of content depending on output rendering mode. + /// + /// Specify how to render the text content. + /// Rendered string based on outputRendering. + public string ToString(OutputRendering outputRendering) + { + if (!_isDecorated) + { + return _text; + } + + if (outputRendering == OutputRendering.Automatic) + { + outputRendering = OutputRendering.Ansi; + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + outputRendering = OutputRendering.PlainText; + } + } + + if (outputRendering == OutputRendering.PlainText) + { + return PlainText; + } + else + { + return _text; + } + } + } +} diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs b/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs index decceac510..4750b099b0 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs @@ -222,6 +222,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format internal override void WriteLine(string s) { CheckStopProcessing(); + // delegate the action to the helper, // that will properly break the string into // screen lines @@ -351,6 +352,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format #if TEST_MULTICELL_ON_SINGLE_CELL_LOCALE s = ((DisplayCellsTest)this._displayCellsPSHost).GenerateTestString(s); #endif + switch (this.WriteStream) { case WriteStreamType.Error: diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 86ff0666e2..f1be0c5acc 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -126,6 +126,9 @@ namespace System.Management.Automation new ExperimentalFeature( name: "PSSubsystemPluginModel", description: "A plugin model for registering and un-registering PowerShell subsystems"), + new ExperimentalFeature( + name: "PSAnsiRendering", + description: "Enable $PSStyle variable to control ANSI rendering of strings"), }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 74fb6f8703..2495a4ac0f 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -4442,6 +4442,13 @@ end { new SessionStateVariableEntry(SpecialVariables.FirstToken, null, string.Empty), new SessionStateVariableEntry(SpecialVariables.StackTrace, null, string.Empty), + // Variable which controls the output rendering + new SessionStateVariableEntry( + SpecialVariables.PSStyle, + PSStyle.Instance, + RunspaceInit.PSStyleDescription, + ScopedItemOptions.None), + // Variable which controls the encoding for piping data to a NativeCommand new SessionStateVariableEntry( SpecialVariables.OutputEncoding, diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index aa241b6c94..ba757722e9 100644 --- a/src/System.Management.Automation/engine/SpecialVariables.cs +++ b/src/System.Management.Automation/engine/SpecialVariables.cs @@ -32,6 +32,10 @@ namespace System.Management.Automation internal static readonly VariablePath OFSVarPath = new VariablePath(OFS); + internal const string PSStyle = "PSStyle"; + + internal static readonly VariablePath PSStyleVarPath = new VariablePath(PSStyle); + internal const string OutputEncoding = "OutputEncoding"; internal static readonly VariablePath OutputEncodingVarPath = new VariablePath(OutputEncoding); diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index dd464ab4e7..6a4a863d23 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -1787,6 +1787,107 @@ namespace System.Management.Automation return true; } + internal static bool ShouldOutputPlainText(bool isHost, bool? supportsVirtualTerminal) + { + var outputRendering = OutputRendering.Ansi; + + if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + if (supportsVirtualTerminal != false) + { + switch (PSStyle.Instance.OutputRendering) + { + case OutputRendering.Automatic: + outputRendering = OutputRendering.Ansi; + break; + case OutputRendering.Host: + outputRendering = isHost ? OutputRendering.Ansi : OutputRendering.PlainText; + break; + default: + outputRendering = PSStyle.Instance.OutputRendering; + break; + } + } + } + + return outputRendering == OutputRendering.PlainText; + } + + internal static string GetOutputString(string s, bool isHost, bool? supportsVirtualTerminal = null, bool isOutputRedirected = false) + { + if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + var sd = new ValueStringDecorated(s); + + if (sd.IsDecorated) + { + var outputRendering = OutputRendering.Ansi; + if (InternalTestHooks.BypassOutputRedirectionCheck) + { + isOutputRedirected = false; + } + + if (isOutputRedirected || ShouldOutputPlainText(isHost, supportsVirtualTerminal)) + { + outputRendering = OutputRendering.PlainText; + } + + s = sd.ToString(outputRendering); + } + } + + return s; + } + + internal enum FormatStyle + { + Reset, + FormatAccent, + ErrorAccent, + Error, + Warning, + Verbose, + Debug, + } + + internal static string GetFormatStyleString(FormatStyle formatStyle) + { + // redirected console gets plaintext output to preserve existing behavior + if (!InternalTestHooks.BypassOutputRedirectionCheck && + ((PSStyle.Instance.OutputRendering == OutputRendering.PlainText) || + (formatStyle == FormatStyle.Error && Console.IsErrorRedirected) || + (formatStyle != FormatStyle.Error && Console.IsOutputRedirected))) + { + return string.Empty; + } + + if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + PSStyle psstyle = PSStyle.Instance; + switch (formatStyle) + { + case FormatStyle.Reset: + return psstyle.Reset; + case FormatStyle.FormatAccent: + return psstyle.Formatting.FormatAccent; + case FormatStyle.ErrorAccent: + return psstyle.Formatting.ErrorAccent; + case FormatStyle.Error: + return psstyle.Formatting.Error; + case FormatStyle.Warning: + return psstyle.Formatting.Warning; + case FormatStyle.Verbose: + return psstyle.Formatting.Verbose; + case FormatStyle.Debug: + return psstyle.Formatting.Debug; + default: + return string.Empty; + } + } + + return string.Empty; + } + #endregion } @@ -1970,6 +2071,7 @@ namespace System.Management.Automation.Internal internal static bool BypassAppLockerPolicyCaching; internal static bool BypassOnlineHelpRetrieval; internal static bool ForcePromptForChoiceDefaultOption; + internal static bool BypassOutputRedirectionCheck; // Stop/Restart/Rename Computer tests internal static bool TestStopComputer; diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 4996ed2493..1e3da98da2 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -619,6 +619,16 @@ namespace System.Management.Automation.Host } resultText = resultText.TrimEnd(); + + if (ExperimentalFeature.IsEnabled("PSAnsiRendering")) + { + var text = new ValueStringDecorated(resultText); + if (text.IsDecorated) + { + resultText = text.ToString(OutputRendering.PlainText); + } + } + foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend(TranscriptionData.SystemTranscript)) { if (transcript != null) diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx index c4a485121c..ac900465d7 100644 --- a/src/System.Management.Automation/resources/RunspaceInit.resx +++ b/src/System.Management.Automation/resources/RunspaceInit.resx @@ -153,6 +153,9 @@ The text encoding used when piping text to a native executable file + + Configuration controlling how text is rendered. + Variable to contain the name of the email server. This can be used instead of the HostName parameter in the Send-MailMessage cmdlet. diff --git a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 new file mode 100644 index 0000000000..27dea30689 --- /dev/null +++ b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 @@ -0,0 +1,85 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'OutputRendering tests' { + BeforeAll { + $PSDefaultParameterValues.Add('It:Skip', (-not $EnabledExperimentalFeatures.Contains('PSAnsiRendering'))) + $th = New-TestHost + $rs = [runspacefactory]::Createrunspace($th) + $rs.open() + $ps = [powershell]::Create() + $ps.Runspace = $rs + } + + AfterAll { + $PSDefaultParameterValues.Remove('It:Skip') + } + + BeforeEach { + if ($null -ne $PSStyle) { + $oldOutputRendering = $PSStyle.OutputRendering + } + } + + AfterEach { + if ($null -ne $PSStyle) { + $PSStyle.OutputRendering = $oldOutputRendering + } + + $ps.Commands.Clear() + } + + It 'OutputRendering works for "" to the host' -TestCases @( + @{ outputRendering = 'automatic'; ansi = $true } + @{ outputRendering = 'host' ; ansi = $true } + @{ outputRendering = 'ansi' ; ansi = $true } + @{ outputRendering = 'plaintext'; ansi = $false } + ) { + param($outputRendering, $ansi) + + $out = pwsh -noprofile -command "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassOutputRedirectionCheck', `$true); `$PSStyle.OutputRendering = '$outputRendering'; write-host '$($PSStyle.Foreground.Green)hello'" + + if ($ansi) { + $out | Should -BeLike "*`e*" -Because ($out | Format-Hex | Out-String) + } + else { + $out | Should -Not -BeLike "*`e*" -Because ($out | Format-Hex | Out-String) + } + } + + It 'OutputRendering works for "" to the pipeline' -TestCases @( + @{ outputRendering = 'automatic'; ansi = $true } + @{ outputRendering = 'host' ; ansi = $false } + @{ outputRendering = 'ansi' ; ansi = $true } + @{ outputRendering = 'plaintext'; ansi = $false } + ) { + param($outputRendering, $ansi) + + $PSStyle.OutputRendering = $outputRendering + $out = "$($PSStyle.Foreground.Green)hello" | Out-String + + if ($ansi) { + $out | Should -BeLike "*`e*" -Because ($out | Format-Hex | Out-String) + } + else { + $out | Should -Not -BeLike "*`e*" -Because ($out | Format-Hex | Out-String) + } + } + + # Error isn't covered here because it has custom formatting + It 'OutputRendering is correct for ' -TestCases @( + @{ stream = 'Verbose' } + @{ stream = 'Debug' } + @{ stream = 'Warning' } + ) { + param($stream) + + if ($stream -ne 'Warning') + { + $switch = "-$stream" + } + + $out = pwsh -noprofile -command "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassOutputRedirectionCheck', `$true); write-$stream $switch 'hello'" + $out | Should -BeExactly "$($PSStyle.Formatting.$stream)$($stream.ToUpper()): hello" + } +} diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 new file mode 100644 index 0000000000..5e02e2e231 --- /dev/null +++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 @@ -0,0 +1,114 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Tests for $PSStyle automatic variable' { + BeforeAll { + $PSDefaultParameterValues.Add('It:Skip', (-not $EnabledExperimentalFeatures.Contains('PSAnsiRendering'))) + $styleDefaults = @{ + Reset = "`e[0m" + BlinkOff = "`e[5m" + Blink = "`e[25m" + BoldOff = "`e[22m" + Bold = "`e[1m" + HiddenOff = "`e[28m" + Hidden = "`e[8m" + ReverseOff = "`e[27m" + Reverse = "`e[7m" + ItalicOff = "`e[23m" + Italic = "`e[3m" + UnderlineOff = "`e[24m" + Underline = "`e[4m" + } + + $formattingDefaults = @{ + FormatAccent = "`e[32;1m" + ErrorAccent = "`e[36;1m" + Error = "`e[31;1m" + Debug = "`e[33;1m" + Verbose = "`e[33;1m" + Warning = "`e[33;1m" + } + + $foregroundDefaults = @{ + Black = "`e[30m" + White = "`e[37m" + DarkGray = "`e[90m" + LightGray = "`e[97m" + Red = "`e[31m" + LightRed = "`e[91m" + Magenta = "`e[35m" + LightMagenta = "`e[95m" + Blue = "`e[34m" + LightBlue = "`e[94m" + Cyan = "`e[36m" + LightCyan = "`e[96m" + Green = "`e[32m" + LightGreen = "`e[92m" + Yellow = "`e[33m" + LightYellow = "`e[93m" + } + + $backgroundDefaults = @{ + Black = "`e[40m" + White = "`e[47m" + DarkGray = "`e[100m" + LightGray = "`e[107m" + Red = "`e[41m" + LightRed = "`e[101m" + Magenta = "`e[45m" + LightMagenta = "`e[105m" + Blue = "`e[44m" + LightBlue = "`e[104m" + Cyan = "`e[46m" + LightCyan = "`e[106m" + Green = "`e[42m" + LightGreen = "`e[102m" + Yellow = "`e[43m" + LightYellow = "`e[103m" + } + + function Get-TestCases($hashtable) { + $testcases = [System.Collections.Generic.List[hashtable]]::new() + foreach ($key in $hashtable.Keys) { + $testcases.Add( + @{ Key = $key; Value = $hashtable[$key] } + ) + } + + return $testcases + } + } + + AfterAll { + $PSDefaultParameterValues.Remove('It:Skip') + } + + It '$PSStyle has correct default for OutputRendering' { + $PSStyle | Should -Not -BeNullOrEmpty + $PSStyle.OutputRendering | Should -BeExactly 'Automatic' + } + + It '$PSStyle has correct defaults for style ' -TestCases (Get-TestCases $styleDefaults) { + param($key, $value) + + $PSStyle.$key | Should -BeExactly $value + } + + It '$PSStyle.Formatting has correct default for ' -TestCases (Get-TestCases $formattingDefaults) { + param($key, $value) + + $PSStyle.Formatting.$key | Should -BeExactly $value + } + + It '$PSStyle.Foreground has correct default for ' -TestCases (Get-TestCases $foregroundDefaults) { + param($key, $value) + + $PSStyle.Foreground.$key | Should -BeExactly $value + } + + It '$PSStyle.Background has correct default for ' -TestCases (Get-TestCases $backgroundDefaults) { + param($key, $value) + + $PSStyle.Background.$key | Should -BeExactly $value + } +}