MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Globalization;6using System.IO;7using System.Management.Automation;8using System.Management.Automation.Host;9using System.Management.Automation.Internal;10using System.Text;11 12// interfaces for host interaction13 14namespace Microsoft.PowerShell.Commands.Internal.Format15{16 /// <summary>17 /// Base class providing support for string manipulation.18 /// This class is a tear off class provided by the LineOutput class.19 /// </summary>20 internal class DisplayCells21 {22 /// <summary>23 /// Calculate the buffer cell length of the given string.24 /// </summary>25 /// <param name="str">String that may contain VT escape sequences.</param>26 /// <returns>Number of buffer cells the string needs to take.</returns>27 internal int Length(string str)28 {29 return Length(str, 0);30 }31 32 /// <summary>33 /// Calculate the buffer cell length of the given string.34 /// </summary>35 /// <param name="str">String that may contain VT escape sequences.</param>36 /// <param name="offset">37 /// When the string doesn't contain VT sequences, it's the starting index.38 /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>39 /// <returns>Number of buffer cells the string needs to take.</returns>40 internal virtual int Length(string str, int offset)41 {42 if (string.IsNullOrEmpty(str))43 {44 return 0;45 }46 47 var valueStrDec = new ValueStringDecorated(str);48 if (valueStrDec.IsDecorated)49 {50 str = valueStrDec.ToString(OutputRendering.PlainText);51 }52 53 int length = 0;54 for (; offset < str.Length; offset++)55 {56 length += CharLengthInBufferCells(str[offset]);57 }58 59 return length;60 }61 62 /// <summary>63 /// Calculate the buffer cell length of the given character.64 /// </summary>65 /// <param name="character"></param>66 /// <returns>Number of buffer cells the character needs to take.</returns>67 internal virtual int Length(char character)68 {69 return CharLengthInBufferCells(character);70 }71 72 /// <summary>73 /// Truncate from the tail of the string.74 /// </summary>75 /// <param name="str">String that may contain VT escape sequences.</param>76 /// <param name="displayCells">Number of buffer cells to fit in.</param>77 /// <returns>Number of non-escape-sequence characters from head of the string that can fit in the space.</returns>78 internal int TruncateTail(string str, int displayCells)79 {80 return TruncateTail(str, offset: 0, displayCells);81 }82 83 /// <summary>84 /// Truncate from the tail of the string.85 /// </summary>86 /// <param name="str">String that may contain VT escape sequences.</param>87 /// <param name="offset">88 /// When the string doesn't contain VT sequences, it's the starting index.89 /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>90 /// <param name="displayCells">Number of buffer cells to fit in.</param>91 /// <returns>Number of non-escape-sequence characters from head of the string that can fit in the space.</returns>92 internal int TruncateTail(string str, int offset, int displayCells)93 {94 var valueStrDec = new ValueStringDecorated(str);95 if (valueStrDec.IsDecorated)96 {97 str = valueStrDec.ToString(OutputRendering.PlainText);98 }99 100 return GetFitLength(str, offset, displayCells, startFromHead: true);101 }102 103 /// <summary>104 /// Truncate from the head of the string.105 /// </summary>106 /// <param name="str">String that may contain VT escape sequences.</param>107 /// <param name="displayCells">Number of buffer cells to fit in.</param>108 /// <returns>Number of non-escape-sequence characters from head of the string that should be skipped.</returns>109 internal int TruncateHead(string str, int displayCells)110 {111 var valueStrDec = new ValueStringDecorated(str);112 if (valueStrDec.IsDecorated)113 {114 str = valueStrDec.ToString(OutputRendering.PlainText);115 }116 117 int tailCount = GetFitLength(str, offset: 0, displayCells, startFromHead: false);118 return str.Length - tailCount;119 }120 121 #region Helpers122 123 protected static int CharLengthInBufferCells(char c)124 {125 // The following is based on http://www.cl.cam.ac.uk/~mgk25/c/wcwidth.c126 // which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt127 bool isWide = c >= 0x1100 &&128 (c <= 0x115f || /* Hangul Jamo init. consonants */129 c == 0x2329 || c == 0x232a ||130 ((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) &&131 c != 0x303f) || /* CJK ... Yi */132 ((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */133 ((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */134 ((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */135 ((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */136 ((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */137 ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0)));138 139 // We can ignore these ranges because .Net strings use surrogate pairs140 // for this range and we do not handle surrogate pairs.141 // (c >= 0x20000 && c <= 0x2fffd) ||142 // (c >= 0x30000 && c <= 0x3fffd)143 return 1 + (isWide ? 1 : 0);144 }145 146 /// <summary>147 /// Given a string and a number of display cells, it computes how many148 /// characters would fit starting from the beginning or end of the string.149 /// </summary>150 /// <param name="str">String to be displayed, which doesn't contain any VT sequences.</param>151 /// <param name="offset">Offset inside the string.</param>152 /// <param name="displayCells">Number of display cells.</param>153 /// <param name="startFromHead">If true compute from the head (i.e. k++) else from the tail (i.e. k--).</param>154 /// <returns>Number of characters that would fit.</returns>155 protected int GetFitLength(string str, int offset, int displayCells, bool startFromHead)156 {157 int filledDisplayCellsCount = 0; // number of cells that are filled in158 int charactersAdded = 0; // number of characters that fit159 int currCharDisplayLen; // scratch variable160 161 int k = startFromHead ? offset : str.Length - 1;162 int kFinal = startFromHead ? str.Length - 1 : offset;163 while (true)164 {165 if ((startFromHead && k > kFinal) || (!startFromHead && k < kFinal))166 {167 break;168 }169 170 // compute the cell number for the current character171 currCharDisplayLen = this.Length(str[k]);172 173 if (filledDisplayCellsCount + currCharDisplayLen > displayCells)174 {175 // if we added this character it would not fit, we cannot continue176 break;177 }178 179 // keep adding, we fit180 filledDisplayCellsCount += currCharDisplayLen;181 charactersAdded++;182 183 // check if we fit exactly184 if (filledDisplayCellsCount == displayCells)185 {186 // exact fit, we cannot add more187 break;188 }189 190 k = startFromHead ? (k + 1) : (k - 1);191 }192 193 return charactersAdded;194 }195 196 #endregion197 }198 199 /// <summary>200 /// Base class providing information about the screen device capabilities201 /// and used to write the output strings to the text output device.202 /// Each device supported will have to derive from it.203 /// Examples of supported devices are:204 /// * Screen Layout: it layers on top of Console and RawConsole205 /// * File: it layers on top of a TextWriter206 /// * In Memory text stream: it layers on top of an in memory buffer207 /// * Printer: it layers on top of a memory buffer then sent to a printer device208 ///209 /// Assumptions:210 /// - Fixed pitch font: layout done in terms of character cells211 /// - character cell layout not affected by bold, reverse screen, color, etc.212 /// - returned values might change from call to call if the specific underlying213 /// implementation allows window resizing.214 /// </summary>215 internal abstract class LineOutput216 {217 /// <summary>218 /// Whether the device requires full buffering of formatting219 /// objects before any processing.220 /// </summary>221 internal virtual bool RequiresBuffering { get { return false; } }222 223 /// <summary>224 /// Delegate the implementor of ExecuteBufferPlayBack should225 /// call to cause the playback to happen when ready to execute.226 /// </summary>227 internal delegate void DoPlayBackCall();228 229 /// <summary>230 /// If RequiresBuffering = true, this call will be made to231 /// start the playback.232 /// </summary>233 internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { }234 235 /// <summary>236 /// The number of columns the current device has.237 /// </summary>238 internal abstract int ColumnNumber { get; }239 240 /// <summary>241 /// The number of rows the current device has.242 /// </summary>243 internal abstract int RowNumber { get; }244 245 /// <summary>246 /// Write a line to the output device.247 /// </summary>248 /// <param name="s">249 /// string to be written to the device250 /// </param>251 internal abstract void WriteLine(string s);252 253 /// <summary>254 /// Write a line of string as raw text to the output device, with no change to the string.255 /// For example, keeping VT escape sequences intact in it.256 /// </summary>257 /// <param name="s">The raw text to be written to the device.</param>258 internal virtual void WriteRawText(string s) => WriteLine(s);259 260 internal WriteStreamType WriteStream261 {262 get;263 set;264 }265 266 /// <summary>267 /// Handle the stop processing signal.268 /// Set a flag that will be checked during operations.269 /// </summary>270 internal void StopProcessing()271 {272 _isStopping = true;273 }274 275 private bool _isStopping;276 277 internal void CheckStopProcessing()278 {279 if (!_isStopping)280 return;281 throw new PipelineStoppedException();282 }283 284 /// <summary>285 /// Return an instance of the display helper tear off.286 /// </summary>287 /// <value></value>288 internal virtual DisplayCells DisplayCells289 {290 get291 {292 CheckStopProcessing();293 // just return the default singleton implementation294 return _displayCellsDefault;295 }296 }297 298 /// <summary>299 /// Singleton used for the default implementation.300 /// NOTE: derived classes may chose to provide a different301 /// implementation by overriding.302 /// </summary>303 protected static DisplayCells _displayCellsDefault = new DisplayCells();304 }305 306 /// <summary>307 /// Helper class to provide line breaking (based on device width)308 /// and embedded newline processing309 /// It needs to be provided with two callbacks for line processing.310 /// </summary>311 internal class WriteLineHelper312 {313 #region callbacks314 315 /// <summary>316 /// Delegate definition.317 /// </summary>318 /// <param name="s">String to write.</param>319 internal delegate void WriteCallback(string s);320 321 /// <summary>322 /// Instance of the delegate previously defined323 /// for line that has EXACTLY this.ncols characters.324 /// </summary>325 private readonly WriteCallback _writeCall = null;326 327 /// <summary>328 /// Instance of the delegate previously defined329 /// for generic line, less that this.ncols characters.330 /// </summary>331 private readonly WriteCallback _writeLineCall = null;332 333 #endregion334 335 private readonly bool _lineWrap;336 337 /// <summary>338 /// Construct an instance, given the two callbacks339 /// NOTE: if the underlying device treats the two cases as the340 /// same, the same delegate can be passed twice.341 /// </summary>342 /// <param name="lineWrap">True if we require line wrapping.</param>343 /// <param name="wlc">Delegate for WriteLine(), must ben non null.</param>344 /// <param name="wc">Delegate for Write(), if null, use the first parameter.</param>345 /// <param name="displayCells">Helper object for manipulating strings.</param>346 internal WriteLineHelper(bool lineWrap, WriteCallback wlc, WriteCallback wc, DisplayCells displayCells)347 {348 if (wlc == null)349 throw PSTraceSource.NewArgumentNullException(nameof(wlc));350 if (displayCells == null)351 throw PSTraceSource.NewArgumentNullException(nameof(displayCells));352 353 _displayCells = displayCells;354 _writeLineCall = wlc;355 _writeCall = wc ?? wlc;356 _lineWrap = lineWrap;357 }358 359 /// <summary>360 /// Main entry point to process a line.361 /// </summary>362 /// <param name="s">String to process.</param>363 /// <param name="cols">Width of the device.</param>364 internal void WriteLine(string s, int cols)365 {366 WriteLineInternal(s, cols);367 }368 369 /// <summary>370 /// Internal helper, needed because it might make recursive calls to itself.371 /// </summary>372 /// <param name="val">String to process.</param>373 /// <param name="cols">Width of the device.</param>374 private void WriteLineInternal(string val, int cols)375 {376 if (string.IsNullOrEmpty(val))377 {378 _writeLineCall(val);379 return;380 }381 382 // If the output is being redirected, then we don't break val383 if (!_lineWrap)384 {385 _writeCall(val);386 return;387 }388 389 // check for line breaks390 List<string> lines = StringManipulationHelper.SplitLines(val);391 392 // process the substrings as separate lines393 for (int k = 0; k < lines.Count; k++)394 {395 // compute the display length of the string396 int displayLength = _displayCells.Length(lines[k]);397 398 if (displayLength < cols)399 {400 // NOTE: this is the case where where System.Console.WriteLine() would work just fine401 _writeLineCall(lines[k]);402 continue;403 }404 405 if (displayLength == cols)406 {407 // NOTE: this is the corner case where System.Console.WriteLine() cannot be called408 _writeCall(lines[k]);409 continue;410 }411 412 // the string does not fit, so we have to wrap around on multiple lines413 string s = lines[k];414 415 while (true)416 {417 // the string is still too long to fit, write the first cols characters418 // and go back for more wraparound419 int headCount = _displayCells.TruncateTail(s, cols);420 WriteLineInternal(s.VtSubstring(0, headCount), cols);421 422 // chop off the first fieldWidth characters, already printed423 s = s.VtSubstring(headCount);424 if (_displayCells.Length(s) <= cols)425 {426 // if we fit, print the tail of the string and we are done427 WriteLineInternal(s, cols);428 break;429 }430 }431 }432 }433 434 private readonly DisplayCells _displayCells;435 }436 437 /// <summary>438 /// Implementation of the ILineOutput interface accepting an instance of a439 /// TextWriter abstract class.440 /// </summary>441 internal sealed class TextWriterLineOutput : LineOutput442 {443 #region ILineOutput methods444 445 /// <summary>446 /// Get the columns on the screen447 /// for files, it is settable at creation time.448 /// </summary>449 internal override int ColumnNumber450 {451 get452 {453 CheckStopProcessing();454 return _columns;455 }456 }457 458 /// <summary>459 /// Get the # of rows on the screen: for files460 /// we return -1, meaning infinite.461 /// </summary>462 internal override int RowNumber463 {464 get465 {466 CheckStopProcessing();467 return -1;468 }469 }470 471 /// <summary>472 /// Write a line by delegating to the writer underneath.473 /// </summary>474 /// <param name="s"></param>475 internal override void WriteLine(string s)476 {477 WriteRawText(PSHostUserInterface.GetOutputString(s, isHost: false));478 }479 480 /// <summary>481 /// Write a raw text by delegating to the writer underneath, with no change to the text.482 /// For example, keeping VT escape sequences intact in it.483 /// </summary>484 /// <param name="s">The raw text to be written to the device.</param>485 internal override void WriteRawText(string s)486 {487 CheckStopProcessing();488 489 if (_suppressNewline)490 {491 _writer.Write(s);492 }493 else494 {495 _writer.WriteLine(s);496 }497 }498 499 #endregion500 501 /// <summary>502 /// Initialization of the object. It must be called before503 /// attempting any operation.504 /// </summary>505 /// <param name="writer">TextWriter to write to.</param>506 /// <param name="columns">Max columns widths for the text.</param>507 internal TextWriterLineOutput(TextWriter writer, int columns)508 {509 _writer = writer;510 _columns = columns;511 }512 513 /// <summary>514 /// Initialization of the object. It must be called before515 /// attempting any operation.516 /// </summary>517 /// <param name="writer">TextWriter to write to.</param>518 /// <param name="columns">Max columns widths for the text.</param>519 /// <param name="suppressNewline">False to add a newline to the end of the output string, true if not.</param>520 internal TextWriterLineOutput(TextWriter writer, int columns, bool suppressNewline)521 : this(writer, columns)522 {523 _suppressNewline = suppressNewline;524 }525 526 private readonly int _columns = 0;527 528 private readonly TextWriter _writer = null;529 530 private readonly bool _suppressNewline = false;531 }532 533 /// <summary>534 /// TextWriter to generate data for the Monad pipeline in a streaming fashion:535 /// the provided callback will be called each time a line is written.536 /// </summary>537 internal class StreamingTextWriter : TextWriter538 {539 #region tracer540 [TraceSource("StreamingTextWriter", "StreamingTextWriter")]541 private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("StreamingTextWriter", "StreamingTextWriter");542 #endregion tracer543 544 /// <summary>545 /// Create an instance by passing a delegate.546 /// </summary>547 /// <param name="writeCall">Delegate to write to.</param>548 /// <param name="culture">Culture for this TextWriter.</param>549 internal StreamingTextWriter(WriteLineCallback writeCall, CultureInfo culture)550 : base(culture)551 {552 if (writeCall == null)553 throw PSTraceSource.NewArgumentNullException(nameof(writeCall));554 555 _writeCall = writeCall;556 }557 558 #region TextWriter overrides559 560 public override Encoding Encoding { get { return new UnicodeEncoding(); } }561 562 public override void WriteLine(string s)563 {564 _writeCall(s);565 }566 567 #endregion568 569 /// <summary>570 /// Delegate definition.571 /// </summary>572 /// <param name="s">String to write.</param>573 internal delegate void WriteLineCallback(string s);574 575 /// <summary>576 /// Instance of the delegate previously defined.577 /// </summary>578 private readonly WriteLineCallback _writeCall = null;579 }580}581 