diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
index 89901b18126..0c38f35960f 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs
@@ -516,7 +516,11 @@ internal override
Visit(ProgressNode node, ArrayList unused, int unusedToo)
{
node.Age = Math.Min(node.Age + 1, Int32.MaxValue - 1);
- node.Style = ProgressNode.RenderStyle.FullPlus;
+
+ node.Style = ProgressNode.IsMinimalProgressRenderingEnabled()
+ ? ProgressNode.RenderStyle.Ansi
+ : node.Style = ProgressNode.RenderStyle.FullPlus;
+
return true;
}
}
@@ -582,6 +586,13 @@ internal override
}
ArrayList result = new ArrayList();
+
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ RenderHelper(result, _topLevelNodes, indentation: 0, maxWidth, rawUI);
+ return (string[])result.ToArray(typeof(string));
+ }
+
string border = StringUtil.Padding(maxWidth);
result.Add(border);
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
index 438ea03c669..ca2694bcabc 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs
@@ -6,6 +6,9 @@
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
+using System.Text;
+
+using Microsoft.PowerShell.Commands.Internal.Format;
using Dbg = System.Management.Automation.Diagnostics;
@@ -20,7 +23,7 @@ namespace Microsoft.PowerShell
ProgressNode : ProgressRecord
{
///
- /// Indicates the various layouts for rendering a particular node. Each style is progressively less terse.
+ /// Indicates the various layouts for rendering a particular node.
///
internal
enum
@@ -40,6 +43,11 @@ namespace Microsoft.PowerShell
/// The node will be displayed the same as Full, plus, the whole StatusDescription and CurrentOperation will be displayed (in multiple lines if needed).
///
FullPlus = 4,
+
+ ///
+ /// The node will be displayed using ANSI escape sequences.
+ ///
+ Ansi = 5,
}
///
@@ -56,7 +64,11 @@ namespace Microsoft.PowerShell
this.PercentComplete = Math.Min(record.PercentComplete, 100);
this.SecondsRemaining = record.SecondsRemaining;
this.RecordType = record.RecordType;
- this.Style = RenderStyle.FullPlus;
+
+ this.Style = IsMinimalProgressRenderingEnabled()
+ ? RenderStyle.Ansi
+ : this.Style = RenderStyle.FullPlus;
+
this.SourceId = sourceId;
}
@@ -98,6 +110,9 @@ namespace Microsoft.PowerShell
case RenderStyle.Minimal:
RenderMinimal(strCollection, indentation, maxWidth, rawUI);
break;
+ case RenderStyle.Ansi:
+ RenderAnsi(strCollection, indentation, maxWidth);
+ break;
case RenderStyle.Invisible:
// do nothing
break;
@@ -336,6 +351,88 @@ private static void RenderFullDescription(string description, string indent, int
maxWidth));
}
+ internal static bool IsMinimalProgressRenderingEnabled()
+ {
+ return ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.View == ProgressView.Minimal;
+ }
+
+ ///
+ /// Renders a node in the "ANSI" style.
+ ///
+ ///
+ /// List of strings to which the node's rendering will be appended.
+ ///
+ ///
+ /// The indentation level in chars at which the node should be rendered.
+ ///
+ ///
+ /// The maximum number of chars that the rendering is allowed to consume.
+ ///
+ private
+ void
+ RenderAnsi(ArrayList strCollection, int indentation, int maxWidth)
+ {
+ string indent = StringUtil.Padding(indentation);
+ string secRemain = string.Empty;
+ if (SecondsRemaining >= 0)
+ {
+ secRemain = SecondsRemaining.ToString() + "s";
+ }
+
+ int secRemainLength = secRemain.Length + 1;
+
+ // limit progress bar to 120 chars as no need to render full width
+ if (PSStyle.Instance.Progress.MaxWidth > 0 && maxWidth > PSStyle.Instance.Progress.MaxWidth)
+ {
+ maxWidth = PSStyle.Instance.Progress.MaxWidth;
+ }
+
+ // 4 is for the extra space and square brackets below and one extra space
+ int barWidth = maxWidth - Activity.Length - indentation - 4;
+
+ var sb = new StringBuilder();
+ int padding = maxWidth + PSStyle.Instance.Progress.Style.Length + PSStyle.Instance.Reverse.Length + PSStyle.Instance.ReverseOff.Length;
+ sb.Append(PSStyle.Instance.Reverse);
+
+ if (StatusDescription.Length > barWidth - secRemainLength)
+ {
+ sb.Append(StatusDescription.Substring(0, barWidth - secRemainLength - 1));
+ sb.Append(PSObjectHelper.Ellipsis);
+ }
+ else
+ {
+ sb.Append(StatusDescription);
+ }
+
+ sb.Append(string.Empty.PadRight(barWidth + PSStyle.Instance.Reverse.Length - sb.Length - secRemainLength));
+ sb.Append(secRemain);
+
+ if (PercentComplete > 0 && PercentComplete < 100)
+ {
+ int barLength = PercentComplete * barWidth / 100;
+ if (barLength >= barWidth)
+ {
+ barLength = barWidth - 1;
+ }
+
+ sb.Insert(barLength + PSStyle.Instance.Reverse.Length, PSStyle.Instance.ReverseOff);
+ }
+ else
+ {
+ sb.Append(PSStyle.Instance.ReverseOff);
+ }
+
+ strCollection.Add(
+ StringUtil.Format(
+ "{0}{1}{2} [{3}]{4}",
+ indent,
+ PSStyle.Instance.Progress.Style,
+ Activity,
+ sb.ToString(),
+ PSStyle.Instance.Reset)
+ .PadRight(padding));
+ }
+
///
/// The nodes that have this node as their parent.
///
@@ -396,6 +493,9 @@ internal int LinesRequiredMethod(PSHostRawUserInterface rawUi, int maxWidth)
case RenderStyle.Invisible:
return 0;
+ case RenderStyle.Ansi:
+ return 1;
+
default:
Dbg.Assert(false, "Unknown RenderStyle value");
break;
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs
index 65f0267d0ed..873cfb483c7 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Management.Automation;
using System.Management.Automation.Host;
using Dbg = System.Management.Automation.Diagnostics;
@@ -70,65 +71,89 @@ class ProgressPane
int rows = tempProgressRegion.GetLength(0);
int cols = tempProgressRegion.GetLength(1);
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ rows = _content.Length;
+ cols = PSStyle.Instance.Progress.MaxWidth;
+ if (cols > _bufSize.Width)
+ {
+ cols = _bufSize.Width;
+ }
+ }
+
_savedCursor = _rawui.CursorPosition;
_location.X = 0;
-#if UNIX
- _location.Y = _rawui.CursorPosition.Y;
-
- // if cursor is not on left edge already move down one line
- if (_rawui.CursorPosition.X != 0)
+ if (!Platform.IsWindows || ProgressNode.IsMinimalProgressRenderingEnabled())
{
- _location.Y++;
- _rawui.CursorPosition = _location;
- }
+ _location.Y = _rawui.CursorPosition.Y;
- // if the cursor is at the bottom, create screen buffer space by scrolling
- int scrollRows = rows - ((_rawui.BufferSize.Height - 1) - _location.Y);
- if (scrollRows > 0)
- {
- // Scroll the console screen up by 'scrollRows'
- var bottomLocation = _location;
- bottomLocation.Y = _rawui.BufferSize.Height;
- _rawui.CursorPosition = bottomLocation;
- for (int i = 0; i < scrollRows; i++)
+ // if cursor is not on left edge already move down one line
+ if (_rawui.CursorPosition.X != 0)
{
- Console.Out.Write('\n');
+ _location.Y++;
+ _rawui.CursorPosition = _location;
}
- _location.Y -= scrollRows;
- _savedCursor.Y -= scrollRows;
- }
+ // if the cursor is at the bottom, create screen buffer space by scrolling
+ int scrollRows = rows - ((_rawui.BufferSize.Height - 1) - _location.Y);
+ if (scrollRows > 0)
+ {
+ // Scroll the console screen up by 'scrollRows'
+ var bottomLocation = _location;
+ bottomLocation.Y = _rawui.BufferSize.Height - 1;
+
+ _rawui.CursorPosition = bottomLocation;
+ for (int i = 0; i < scrollRows; i++)
+ {
+ Console.Out.Write('\n');
+ }
+
+ _location.Y -= scrollRows;
+ _savedCursor.Y -= scrollRows;
+ }
- // create cleared region to clear progress bar later
- _savedRegion = tempProgressRegion;
- for (int row = 0; row < rows; row++)
- {
- for (int col = 0; col < cols; col++)
+ // create cleared region to clear progress bar later
+ _savedRegion = tempProgressRegion;
+ if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAnsiProgressFeatureName) && PSStyle.Instance.Progress.View != ProgressView.Minimal)
{
- _savedRegion[row, col].Character = ' ';
+ for (int row = 0; row < rows; row++)
+ {
+ for (int col = 0; col < cols; col++)
+ {
+ _savedRegion[row, col].Character = ' ';
+ }
+ }
}
- }
- // put cursor back to where output should be
- _rawui.CursorPosition = _location;
-#else
- _location = _rawui.WindowPosition;
+ // put cursor back to where output should be
+ _rawui.CursorPosition = _location;
+ }
+ else
+ {
+ _location = _rawui.WindowPosition;
- // We have to show the progress pane in the first column, as the screen buffer at any point might contain
- // a CJK double-cell characters, which makes it impractical to try to find a position where the pane would
- // not slice a character. Column 0 is the only place where we know for sure we can place the pane.
+ // We have to show the progress pane in the first column, as the screen buffer at any point might contain
+ // a CJK double-cell characters, which makes it impractical to try to find a position where the pane would
+ // not slice a character. Column 0 is the only place where we know for sure we can place the pane.
- _location.Y = Math.Min(_location.Y + 2, _bufSize.Height);
+ _location.Y = Math.Min(_location.Y + 2, _bufSize.Height);
- // Save off the current contents of the screen buffer in the region that we will occupy
- _savedRegion =
- _rawui.GetBufferContents(
- new Rectangle(_location.X, _location.Y, _location.X + cols - 1, _location.Y + rows - 1));
-#endif
+ // Save off the current contents of the screen buffer in the region that we will occupy
+ _savedRegion =
+ _rawui.GetBufferContents(
+ new Rectangle(_location.X, _location.Y, _location.X + cols - 1, _location.Y + rows - 1));
+ }
- // replace the saved region in the screen buffer with our progress display
- _rawui.SetBufferContents(_location, tempProgressRegion);
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ WriteContent();
+ }
+ else
+ {
+ // replace the saved region in the screen buffer with our progress display
+ _rawui.SetBufferContents(_location, tempProgressRegion);
+ }
}
}
@@ -142,12 +167,37 @@ class ProgressPane
{
if (IsShowing)
{
- // It would be nice if we knew that the saved region could be kept for the next time Show is called, but alas,
- // we have no way of knowing if the screen buffer has changed since we were hidden. By "no good way" I mean that
- // detecting a change would be at least as expensive as chucking the savedRegion and rebuilding it. And it would
- // be very complicated.
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ _rawui.CursorPosition = _location;
+ int maxWidth = PSStyle.Instance.Progress.MaxWidth;
+ if (maxWidth > _bufSize.Width)
+ {
+ maxWidth = _bufSize.Width;
+ }
+
+ for (int i = 0; i < _savedRegion.GetLength(1); i++)
+ {
+ if (i < _savedRegion.GetLength(1) - 1)
+ {
+ Console.Out.WriteLine(string.Empty.PadRight(maxWidth));
+ }
+ else
+ {
+ Console.Out.Write(string.Empty.PadRight(maxWidth));
+ }
+ }
+ }
+ else
+ {
+ // It would be nice if we knew that the saved region could be kept for the next time Show is called, but alas,
+ // we have no way of knowing if the screen buffer has changed since we were hidden. By "no good way" I mean that
+ // detecting a change would be at least as expensive as chucking the savedRegion and rebuilding it. And it would
+ // be very complicated.
+
+ _rawui.SetBufferContents(_location, _savedRegion);
+ }
- _rawui.SetBufferContents(_location, _savedRegion);
_savedRegion = null;
_rawui.CursorPosition = _savedCursor;
}
@@ -173,8 +223,8 @@ class ProgressPane
int maxWidth = _bufSize.Width;
int maxHeight = Math.Max(5, _rawui.WindowSize.Height / 3);
- string[] contents = pendingProgress.Render(maxWidth, maxHeight, _rawui);
- if (contents == null)
+ _content = pendingProgress.Render(maxWidth, maxHeight, _rawui);
+ if (_content == null)
{
// There's nothing to show.
@@ -183,9 +233,21 @@ class ProgressPane
return;
}
- // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here...
+ BufferCell[,] newRegion;
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ // Legacy progress rendering relies on a BufferCell which defines a character, foreground color, and background color
+ // per cell. This model doesn't work with ANSI escape sequences. However, there is existing logic on rendering that
+ // relies on the existence of the BufferCell to know if something has been rendered previously. Here we are creating
+ // an empty BufferCell, but using the second dimension to capture the number of rows so that we can clear that many
+ // elsewhere in Hide().
+ newRegion = new BufferCell[0, _content.Length];
+ }
+ else
+ {
+ newRegion = _rawui.NewBufferCellArray(_content, _ui.ProgressForegroundColor, _ui.ProgressBackgroundColor);
+ }
- BufferCell[,] newRegion = _rawui.NewBufferCellArray(contents, _ui.ProgressForegroundColor, _ui.ProgressBackgroundColor);
Dbg.Assert(newRegion != null, "NewBufferCellArray has failed!");
if (_progressRegion == null)
@@ -223,8 +285,40 @@ class ProgressPane
}
else
{
- _rawui.SetBufferContents(_location, _progressRegion);
+ if (ProgressNode.IsMinimalProgressRenderingEnabled())
+ {
+ WriteContent();
+ }
+ else
+ {
+ _rawui.SetBufferContents(_location, _progressRegion);
+ }
+ }
+ }
+ }
+
+ private void WriteContent()
+ {
+ if (_content is not null)
+ {
+ Console.CursorVisible = false;
+ var currentPosition = _rawui.CursorPosition;
+ _rawui.CursorPosition = _location;
+
+ for (int i = 0; i < _content.Length; i++)
+ {
+ if (i < _content.Length - 1)
+ {
+ Console.Out.WriteLine(_content[i]);
+ }
+ else
+ {
+ Console.Out.Write(_content[i]);
+ }
}
+
+ _rawui.CursorPosition = currentPosition;
+ Console.CursorVisible = true;
}
}
@@ -233,6 +327,7 @@ class ProgressPane
private Size _bufSize;
private BufferCell[,] _savedRegion;
private BufferCell[,] _progressRegion;
+ private string[] _content;
private readonly PSHostRawUserInterface _rawui;
private readonly ConsoleHostUserInterface _ui;
}
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 d881f551b31..d9763164679 100644
--- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs
+++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs
@@ -272,6 +272,10 @@ internal static IEnumerable GetFormatData()
"System.Management.Automation.PSStyle+FormattingData",
ViewsOf_System_Management_Automation_PSStyleFormattingData());
+ yield return new ExtendedTypeDefinition(
+ "System.Management.Automation.PSStyle+ProgressConfiguration",
+ ViewsOf_System_Management_Automation_PSStyleProgressConfiguration());
+
yield return new ExtendedTypeDefinition(
"System.Management.Automation.PSStyle+ForegroundColor",
ViewsOf_System_Management_Automation_PSStyleForegroundColor());
@@ -2048,6 +2052,9 @@ private static IEnumerable ViewsOf_System_Management_Autom
.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(@"""$($_.Progress.Style)$($_.Progress.Style.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Progress.Style")
+ .AddItemScriptBlock(@"""$($_.Progress.MaxWidth)""", label: "Progress.MaxWidth")
+ .AddItemScriptBlock(@"""$($_.Progress.View)""", label: "Progress.View")
.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")
@@ -2099,6 +2106,18 @@ private static IEnumerable ViewsOf_System_Management_Autom
.EndList());
}
+ private static IEnumerable ViewsOf_System_Management_Automation_PSStyleProgressConfiguration()
+ {
+ yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ProgressConfiguration",
+ ListControl.Create()
+ .StartEntry()
+ .AddItemScriptBlock(@"""$($_.Style)$($_.Style.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Style")
+ .AddItemProperty(@"MaxWidth")
+ .AddItemProperty(@"View")
+ .EndEntry()
+ .EndList());
+ }
+
private static IEnumerable ViewsOf_System_Management_Automation_PSStyleForegroundColor()
{
yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor",
diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs
index 9d6876c44e0..aa00bdf9e18 100644
--- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs
+++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs
@@ -23,16 +23,28 @@ public enum OutputRendering
}
#endregion OutputRendering
+ ///
+ /// Defines the options for views of progress rendering.
+ ///
+ public enum ProgressView
+ {
+ /// Render progress using minimal space.
+ Minimal = 0,
+
+ /// Classic rendering of progress.
+ Classic = 1,
+ }
+
#region PSStyle
///
/// Contains configuration for how PowerShell renders text.
///
- public class PSStyle
+ public sealed class PSStyle
{
///
/// Contains foreground colors.
///
- public class ForegroundColor
+ public sealed class ForegroundColor
{
///
/// Gets the color black.
@@ -147,7 +159,7 @@ public string FromRgb(int rgb)
///
/// Contains background colors.
///
- public class BackgroundColor
+ public sealed class BackgroundColor
{
///
/// Gets the color black.
@@ -259,10 +271,31 @@ public string FromRgb(int rgb)
}
}
+ ///
+ /// Contains configuration for the progress bar visualization.
+ ///
+ public sealed class ProgressConfiguration
+ {
+ ///
+ /// Gets or sets the style for progress bar.
+ ///
+ public string Style { get; set; } = "\x1b[33;1m";
+
+ ///
+ /// Gets or sets the max width of the progress bar.
+ ///
+ public int MaxWidth { get; set; } = 120;
+
+ ///
+ /// Gets or sets the view for progress bar.
+ ///
+ public ProgressView View { get; set; } = ProgressView.Minimal;
+ }
+
///
/// Contains formatting styles for steams and objects.
///
- public class FormattingData
+ public sealed class FormattingData
{
///
/// Gets or sets the accent style for formatting.
@@ -391,6 +424,11 @@ public string FormatHyperlink(string text, Uri link)
///
public FormattingData Formatting { get; }
+ ///
+ /// Gets the configuration for progress rendering.
+ ///
+ public ProgressConfiguration Progress { get; }
+
///
/// Gets foreground colors.
///
@@ -406,6 +444,7 @@ public string FormatHyperlink(string text, Uri link)
private PSStyle()
{
Formatting = new FormattingData();
+ Progress = new ProgressConfiguration();
Foreground = new ForegroundColor();
Background = new BackgroundColor();
}
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index f1be0c5acc8..07fb6ed59c2 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -21,6 +21,7 @@ public class ExperimentalFeature
#region Const Members
internal const string EngineSource = "PSEngine";
+ internal const string PSAnsiProgressFeatureName = "PSAnsiProgress";
#endregion
@@ -129,6 +130,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: "PSAnsiRendering",
description: "Enable $PSStyle variable to control ANSI rendering of strings"),
+ new ExperimentalFeature(
+ name: PSAnsiProgressFeatureName,
+ description: "Enable lightweight progress bar that leverages ANSI codes for rendering"),
};
EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures);