MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.Specialized;7using System.Diagnostics;8using System.Management.Automation;9using System.Management.Automation.Internal;10using System.Text;11 12namespace Microsoft.PowerShell.Commands.Internal.Format13{14 /// <summary>15 /// Class to write object properties in list form by using16 /// the host screen interfaces.17 /// </summary>18 internal class ListWriter19 {20 /// <summary>21 /// Labels already padded with blanks, separator characters, etc.22 /// </summary>23 private string[] _propertyLabels;24 25 /// <summary>26 /// Display length of the property labels in the array (all the same length)27 /// </summary>28 private int _propertyLabelsDisplayLength = 0;29 30 /// <summary>31 /// Column width of the screen.32 /// </summary>33 private int _columnWidth = 0;34 35 /// <summary>36 /// A cached string builder used within this type to reduce creation of temporary strings.37 /// </summary>38 private readonly StringBuilder _cachedBuilder = new();39 40 /// <summary>41 /// </summary>42 /// <param name="propertyNames">Names of the properties to display.</param>43 /// <param name="screenColumnWidth">Column width of the screen.</param>44 /// <param name="dc">Instance of the DisplayCells helper object.</param>45 internal void Initialize(string[] propertyNames, int screenColumnWidth, DisplayCells dc)46 {47 _columnWidth = screenColumnWidth;48 if (propertyNames == null || propertyNames.Length == 0)49 {50 // there is nothing to show51 _disabled = true;52 return;53 }54 55 _disabled = false;56 57 Debug.Assert(propertyNames != null, "propertyNames is null");58 Debug.Assert(propertyNames.Length > 0, "propertyNames has zero length");59 60 // assess the useful widths61 if ((screenColumnWidth - Separator.Length - MinFieldWidth - MinLabelWidth) < 0)62 {63 // we do not have enough space for any meaningful display64 _disabled = true;65 return;66 }67 68 // check if we have to truncate the labels69 int maxAllowableLabelLength = screenColumnWidth - Separator.Length - MinFieldWidth;70 if (InternalTestHooks.ForceFormatListFixedLabelWidth)71 {72 maxAllowableLabelLength = 10;73 }74 75 // find out the max display length (cell count) of the property names76 _propertyLabelsDisplayLength = 0; // reset max77 78 // cache the cell lengths for each property79 Span<int> propertyNameCellCounts = propertyNames.Length <= OutCommandInner.StackAllocThreshold ? stackalloc int[propertyNames.Length] : new int[propertyNames.Length];80 for (int k = 0; k < propertyNames.Length; k++)81 {82 Debug.Assert(propertyNames[k] != null, "propertyNames[k] is null");83 propertyNameCellCounts[k] = dc.Length(propertyNames[k]);84 if (propertyNameCellCounts[k] > _propertyLabelsDisplayLength)85 _propertyLabelsDisplayLength = propertyNameCellCounts[k];86 }87 88 if (_propertyLabelsDisplayLength > maxAllowableLabelLength)89 {90 // need to truncate91 _propertyLabelsDisplayLength = maxAllowableLabelLength;92 }93 94 _propertyLabels = new string[propertyNames.Length];95 96 for (int k = 0; k < propertyNames.Length; k++)97 {98 string propertyName = propertyNames[k];99 if (propertyNameCellCounts[k] < _propertyLabelsDisplayLength)100 {101 // shorter than the max, add padding102 _propertyLabels[k] = propertyName + StringUtil.Padding(_propertyLabelsDisplayLength - propertyNameCellCounts[k]);103 }104 else if (propertyNameCellCounts[k] > _propertyLabelsDisplayLength)105 {106 // longer than the max, clip107 _propertyLabels[k] = propertyName.VtSubstring(0, dc.TruncateTail(propertyName, _propertyLabelsDisplayLength));108 }109 else110 {111 _propertyLabels[k] = propertyName;112 }113 114 _propertyLabels[k] += Separator;115 }116 117 _propertyLabelsDisplayLength += Separator.Length;118 }119 120 /// <summary>121 /// Write the values of the properties of an object.122 /// </summary>123 /// <param name="values">Array with the values in form of formatted strings.</param>124 /// <param name="lo">LineOutput interface to write to.</param>125 internal void WriteProperties(string[] values, LineOutput lo)126 {127 if (_disabled)128 return;129 130 string[] valuesToPrint = null;131 if (values == null)132 {133 // we have nothing, but we have to create an empty array134 valuesToPrint = new string[_propertyLabels.Length];135 for (int k = 0; k < _propertyLabels.Length; k++)136 valuesToPrint[k] = string.Empty;137 }138 else if (values.Length < _propertyLabels.Length)139 {140 // need to pad to the end of the array141 valuesToPrint = new string[_propertyLabels.Length];142 for (int k = 0; k < _propertyLabels.Length; k++)143 {144 if (k < values.Length)145 valuesToPrint[k] = values[k];146 else147 valuesToPrint[k] = string.Empty;148 }149 }150 else if (values.Length > _propertyLabels.Length)151 {152 // need to trim153 valuesToPrint = new string[_propertyLabels.Length];154 for (int k = 0; k < _propertyLabels.Length; k++)155 valuesToPrint[k] = values[k];156 }157 else158 {159 // perfect match160 valuesToPrint = values;161 }162 163 Debug.Assert(lo != null, "LineOutput is null");164 165 for (int k = 0; k < _propertyLabels.Length; k++)166 {167 WriteProperty(k, valuesToPrint[k], lo);168 }169 }170 171 /// <summary>172 /// Helper, writing a single property to the screen.173 /// It wraps the value of the property if it is tool long to fit.174 /// </summary>175 /// <param name="k">Index of property to write.</param>176 /// <param name="propertyValue">String value of the property to write.</param>177 /// <param name="lo">LineOutput interface to write to.</param>178 private void WriteProperty(int k, string propertyValue, LineOutput lo)179 {180 propertyValue ??= string.Empty;181 182 // make sure we honor embedded newlines183 List<string> lines = StringManipulationHelper.SplitLines(propertyValue);184 185 // padding to use in the lines after the first186 string padding = null;187 188 for (int i = 0; i < lines.Count; i++)189 {190 string prependString = null;191 192 if (i == 0)193 prependString = _propertyLabels[k];194 else195 {196 padding ??= StringUtil.Padding(_propertyLabelsDisplayLength);197 198 prependString = padding;199 }200 201 WriteSingleLineHelper(prependString, lines[i], lo);202 }203 }204 205 /// <summary>206 /// Internal helper to split a line that is too long to fit and pad it to the left207 /// with a given string.208 /// </summary>209 /// <param name="prependString">String to add to the left.</param>210 /// <param name="line">Line to print.</param>211 /// <param name="lo">LineOutput to write to.</param>212 private void WriteSingleLineHelper(string prependString, string line, LineOutput lo)213 {214 line ??= string.Empty;215 216 // compute the width of the field for the value string (in screen cells)217 int fieldCellCount = _columnWidth - _propertyLabelsDisplayLength;218 219 // split the lines220 StringCollection sc = StringManipulationHelper.GenerateLines(lo.DisplayCells, line, fieldCellCount, fieldCellCount);221 222 // The padding to use in the lines after the first.223 string headPadding = null;224 225 // The VT style used for the list label.226 string style = PSStyle.Instance.Formatting.FormatAccent;227 string reset = PSStyle.Instance.Reset;228 229 // display the string collection230 for (int k = 0; k < sc.Count; k++)231 {232 string str = sc[k];233 _cachedBuilder.Clear();234 235 if (k == 0)236 {237 if (string.IsNullOrWhiteSpace(prependString) || style == string.Empty)238 {239 // - Sometimes 'prependString' is just padding white spaces, and we don't240 // need to add formatting escape sequences in such a case.241 // - Otherwise, if the style is an empty string, then the user has chosen242 // to not apply a style to the list label.243 _cachedBuilder.Append(prependString).Append(str);244 }245 else246 {247 // Apply the style to the list label.248 _cachedBuilder249 .Append(style)250 .Append(prependString)251 .Append(reset)252 .Append(str);253 }254 }255 else256 {257 // Lazily calculate the padding to use for the subsequent lines as it's quite often that only the first line exists.258 headPadding ??= StringUtil.Padding(_propertyLabelsDisplayLength);259 _cachedBuilder.Append(headPadding).Append(str);260 }261 262 if (str.Contains(ValueStringDecorated.ESC) && !str.AsSpan().TrimEnd().EndsWith(reset, StringComparison.Ordinal))263 {264 _cachedBuilder.Append(reset);265 }266 267 lo.WriteLine(_cachedBuilder.ToString());268 }269 }270 271 /// <summary>272 /// Set to true when the width of the screen is too small to do anything useful.273 /// </summary>274 private bool _disabled = false;275 276 private const string Separator = " : ";277 278 /// <summary>279 /// Minimum width for the property label field.280 /// </summary>281 private const int MinLabelWidth = 1;282 283 /// <summary>284 /// Minimum width for the property value field.285 /// </summary>286 private const int MinFieldWidth = 1;287 }288}289 