Enable ANSI formatting via $PSStyle and support suppressing ANSI output (#13758)

This commit is contained in:
Steve Lee
2020-12-11 10:33:57 -08:00
committed by GitHub
parent a106d1d4b6
commit a99ea2acd2
14 changed files with 1102 additions and 15 deletions
@@ -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);
}
}
}
@@ -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<FormatViewDefinition> 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<FormatViewDefinition> 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<FormatViewDefinition> 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<FormatViewDefinition> 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<FormatViewDefinition> 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());
}
}
}
@@ -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);
@@ -0,0 +1,404 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace System.Management.Automation
{
#region OutputRendering
/// <summary>
/// Defines the options for output rendering.
/// </summary>
public enum OutputRendering
{
/// <summary>Automatic by PowerShell.</summary>
Automatic = 0,
/// <summary>Render as plaintext.</summary>
PlainText = 1,
/// <summary>Render as ANSI.</summary>
Ansi = 2,
/// <summary>Render ANSI only to host.</summary>
Host = 3,
}
#endregion OutputRendering
#region PSStyle
/// <summary>
/// Contains configuration for how PowerShell renders text.
/// </summary>
public class PSStyle
{
/// <summary>
/// Contains foreground colors.
/// </summary>
public class ForegroundColor
{
/// <summary>
/// Gets the color black.
/// </summary>
public string Black { get; } = "\x1b[30m";
/// <summary>
/// Gets the color blue.
/// </summary>
public string Blue { get; } = "\x1b[34m";
/// <summary>
/// Gets the color cyan.
/// </summary>
public string Cyan { get; } = "\x1b[36m";
/// <summary>
/// Gets the color dark gray.
/// </summary>
public string DarkGray { get; } = "\x1b[90m";
/// <summary>
/// Gets the color green.
/// </summary>
public string Green { get; } = "\x1b[32m";
/// <summary>
/// Gets the color light blue.
/// </summary>
public string LightBlue { get; } = "\x1b[94m";
/// <summary>
/// Gets the color light cyan.
/// </summary>
public string LightCyan { get; } = "\x1b[96m";
/// <summary>
/// Gets the color light gray.
/// </summary>
public string LightGray { get; } = "\x1b[97m";
/// <summary>
/// Gets the color light green.
/// </summary>
public string LightGreen { get; } = "\x1b[92m";
/// <summary>
/// Gets the color light magenta.
/// </summary>
public string LightMagenta { get; } = "\x1b[95m";
/// <summary>
/// Gets the color light red.
/// </summary>
public string LightRed { get; } = "\x1b[91m";
/// <summary>
/// Gets the color light yellow.
/// </summary>
public string LightYellow { get; } = "\x1b[93m";
/// <summary>
/// Gets the color magenta.
/// </summary>
public string Magenta { get; } = "\x1b[35m";
/// <summary>
/// Gets the color read.
/// </summary>
public string Red { get; } = "\x1b[31m";
/// <summary>
/// Gets the color white.
/// </summary>
public string White { get; } = "\x1b[37m";
/// <summary>
/// Gets the color yellow.
/// </summary>
public string Yellow { get; } = "\x1b[33m";
/// <summary>
/// Set as RGB (Red, Green, Blue).
/// </summary>
/// <param name="red">Byte value representing red.</param>
/// <param name="green">Byte value representing green.</param>
/// <param name="blue">Byte value representing blue.</param>
/// <returns>String representing ANSI code for RGB value.</returns>
public string FromRgb(byte red, byte green, byte blue)
{
return $"\x1b[38;2;{red};{green};{blue}m";
}
/// <summary>
/// The color set as RGB as a single number.
/// </summary>
/// <param name="rgb">RGB value specified as an integer.</param>
/// <returns>String representing ANSI code for RGB value.</returns>
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);
}
}
/// <summary>
/// Contains background colors.
/// </summary>
public class BackgroundColor
{
/// <summary>
/// Gets the color black.
/// </summary>
public string Black { get; } = "\x1b[40m";
/// <summary>
/// Gets the color blue.
/// </summary>
public string Blue { get; } = "\x1b[44m";
/// <summary>
/// Gets the color cyan.
/// </summary>
public string Cyan { get; } = "\x1b[46m";
/// <summary>
/// Gets the color dark gray.
/// </summary>
public string DarkGray { get; } = "\x1b[100m";
/// <summary>
/// Gets the color green.
/// </summary>
public string Green { get; } = "\x1b[42m";
/// <summary>
/// Gets the color light blue.
/// </summary>
public string LightBlue { get; } = "\x1b[104m";
/// <summary>
/// Gets the color light cyan.
/// </summary>
public string LightCyan { get; } = "\x1b[106m";
/// <summary>
/// Gets the color light gray.
/// </summary>
public string LightGray { get; } = "\x1b[107m";
/// <summary>
/// Gets the color light green.
/// </summary>
public string LightGreen { get; } = "\x1b[102m";
/// <summary>
/// Gets the color light magenta.
/// </summary>
public string LightMagenta { get; } = "\x1b[105m";
/// <summary>
/// Gets the color light red.
/// </summary>
public string LightRed { get; } = "\x1b[101m";
/// <summary>
/// Gets the color light yellow.
/// </summary>
public string LightYellow { get; } = "\x1b[103m";
/// <summary>
/// Gets the color magenta.
/// </summary>
public string Magenta { get; } = "\x1b[45m";
/// <summary>
/// Gets the color read.
/// </summary>
public string Red { get; } = "\x1b[41m";
/// <summary>
/// Gets the color white.
/// </summary>
public string White { get; } = "\x1b[47m";
/// <summary>
/// Gets the color yellow.
/// </summary>
public string Yellow { get; } = "\x1b[43m";
/// <summary>
/// The color set as RGB (Red, Green, Blue).
/// </summary>
/// <param name="red">Byte value representing red.</param>
/// <param name="green">Byte value representing green.</param>
/// <param name="blue">Byte value representing blue.</param>
/// <returns>String representing ANSI code for RGB value.</returns>
public string FromRgb(byte red, byte green, byte blue)
{
return $"\x1b[48;2;{red};{green};{blue}m";
}
/// <summary>
/// The color set as RGB as a single number.
/// </summary>
/// <param name="rgb">RGB value specified as an integer.</param>
/// <returns>String representing ANSI code for RGB value.</returns>
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);
}
}
/// <summary>
/// Contains formatting styles for steams and objects.
/// </summary>
public class FormattingData
{
/// <summary>
/// Gets or sets the accent style for formatting.
/// </summary>
public string FormatAccent { get; set; } = "\x1b[32;1m";
/// <summary>
/// Gets or sets the accent style for errors.
/// </summary>
public string ErrorAccent { get; set; } = "\x1b[36;1m";
/// <summary>
/// Gets or sets the style for error messages.
/// </summary>
public string Error { get; set; } = "\x1b[31;1m";
/// <summary>
/// Gets or sets the style for warning messages.
/// </summary>
public string Warning { get; set; } = "\x1b[33;1m";
/// <summary>
/// Gets or sets the style for verbose messages.
/// </summary>
public string Verbose { get; set; } = "\x1b[33;1m";
/// <summary>
/// Gets or sets the style for debug messages.
/// </summary>
public string Debug { get; set; } = "\x1b[33;1m";
}
/// <summary>
/// Gets or sets the rendering mode for output.
/// </summary>
public OutputRendering OutputRendering { get; set; } = OutputRendering.Automatic;
/// <summary>
/// Gets value to turn off all attributes.
/// </summary>
public string Reset { get; } = "\x1b[0m";
/// <summary>
/// Gets value to turn off blink.
/// </summary>
public string BlinkOff { get; } = "\x1b[5m";
/// <summary>
/// Gets value to turn on blink.
/// </summary>
public string Blink { get; } = "\x1b[25m";
/// <summary>
/// Gets value to turn off bold.
/// </summary>
public string BoldOff { get; } = "\x1b[22m";
/// <summary>
/// Gets value to turn on blink.
/// </summary>
public string Bold { get; } = "\x1b[1m";
/// <summary>
/// Gets value to turn on hidden.
/// </summary>
public string Hidden { get; } = "\x1b[8m";
/// <summary>
/// Gets value to turn off hidden.
/// </summary>
public string HiddenOff { get; } = "\x1b[28m";
/// <summary>
/// Gets value to turn on reverse.
/// </summary>
public string Reverse { get; } = "\x1b[7m";
/// <summary>
/// Gets value to turn off reverse.
/// </summary>
public string ReverseOff { get; } = "\x1b[27m";
/// <summary>
/// Gets value to turn off standout.
/// </summary>
public string ItalicOff { get; } = "\x1b[23m";
/// <summary>
/// Gets value to turn on standout.
/// </summary>
public string Italic { get; } = "\x1b[3m";
/// <summary>
/// Gets value to turn off underlined.
/// </summary>
public string UnderlineOff { get; } = "\x1b[24m";
/// <summary>
/// Gets value to turn on underlined.
/// </summary>
public string Underline { get; } = "\x1b[4m";
/// <summary>
/// Gets the formatting rendering settings.
/// </summary>
public FormattingData Formatting { get; }
/// <summary>
/// Gets foreground colors.
/// </summary>
public ForegroundColor Foreground { get; }
/// <summary>
/// Gets background colors.
/// </summary>
public BackgroundColor Background { get; }
private static readonly PSStyle s_psstyle = new PSStyle();
private PSStyle()
{
Formatting = new FormattingData();
Foreground = new ForegroundColor();
Background = new BackgroundColor();
}
/// <summary>
/// Gets singleton instance.
/// </summary>
public static PSStyle Instance
{
get
{
return s_psstyle;
}
}
}
#endregion PSStyle
}
@@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System.Text.RegularExpressions;
namespace System.Management.Automation.Internal
{
/// <summary>
/// Extensions to String type to calculate and render decorated content.
/// </summary>
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;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="StringDecorated"/> class.
/// </summary>
/// <param name="text">The input string.</param>
public StringDecorated(string text)
{
_text = text;
_isDecorated = text.Contains(ValueStringDecorated.ESC);
}
/// <summary>
/// Gets a value indicating whether the string contains decoration.
/// </summary>
/// <returns>Boolean if the string contains decoration.</returns>
public bool IsDecorated => _isDecorated;
/// <summary>
/// Gets the length of content sans escape sequences.
/// </summary>
/// <returns>Length of content sans escape sequences.</returns>
public int ContentLength => PlainText.Length;
/// <summary>
/// Render the decorarted string using automatic output rendering.
/// </summary>
/// <returns>Rendered string based on automatic output rendering.</returns>
public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text;
/// <summary>
/// Return string representation of content depending on output rendering mode.
/// </summary>
/// <param name="outputRendering">Specify how to render the text content.</param>
/// <returns>Rendered string based on outputRendering.</returns>
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);
/// <summary>
/// Initializes a new instance of the <see cref="ValueStringDecorated"/> struct.
/// </summary>
/// <param name="text">The input string.</param>
public ValueStringDecorated(string text)
{
_text = text;
_isDecorated = text.Contains(ESC);
_plaintextcontent = null;
}
/// <summary>
/// Gets a value indicating whether the string contains decoration.
/// </summary>
/// <returns>Boolean if the string contains decoration.</returns>
public bool IsDecorated => _isDecorated;
/// <summary>
/// Gets the length of content sans escape sequences.
/// </summary>
/// <returns>Length of content sans escape sequences.</returns>
public int ContentLength => PlainText.Length;
/// <summary>
/// Render the decorarted string using automatic output rendering.
/// </summary>
/// <returns>Rendered string based on automatic output rendering.</returns>
public override string ToString() => _isDecorated ? ToString(OutputRendering.Automatic) : _text;
/// <summary>
/// Return string representation of content depending on output rendering mode.
/// </summary>
/// <param name="outputRendering">Specify how to render the text content.</param>
/// <returns>Rendered string based on outputRendering.</returns>
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;
}
}
}
}
@@ -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:
@@ -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<ExperimentalFeature>(engineFeatures);
@@ -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,
@@ -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);
@@ -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;
@@ -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<TranscriptionOption>(TranscriptionData.SystemTranscript))
{
if (transcript != null)
@@ -153,6 +153,9 @@
<data name="OutputEncodingDescription" xml:space="preserve">
<value>The text encoding used when piping text to a native executable file</value>
</data>
<data name="PSStyleDescription" xml:space="preserve">
<value>Configuration controlling how text is rendered.</value>
</data>
<data name="PSEmailServerDescription" xml:space="preserve">
<value>Variable to contain the name of the email server. This can be used instead of the HostName parameter in the Send-MailMessage cmdlet.</value>
</data>
@@ -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 "<outputRendering>" 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 "<outputRendering>" 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 <stream>' -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"
}
}
@@ -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 <key>' -TestCases (Get-TestCases $styleDefaults) {
param($key, $value)
$PSStyle.$key | Should -BeExactly $value
}
It '$PSStyle.Formatting has correct default for <key>' -TestCases (Get-TestCases $formattingDefaults) {
param($key, $value)
$PSStyle.Formatting.$key | Should -BeExactly $value
}
It '$PSStyle.Foreground has correct default for <key>' -TestCases (Get-TestCases $foregroundDefaults) {
param($key, $value)
$PSStyle.Foreground.$key | Should -BeExactly $value
}
It '$PSStyle.Background has correct default for <key>' -TestCases (Get-TestCases $backgroundDefaults) {
param($key, $value)
$PSStyle.Background.$key | Should -BeExactly $value
}
}