Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
ComplexWriter.cs853 linesDownload Raw Back to common
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Collections.Specialized;8using System.Globalization;9using System.Management.Automation;10using System.Management.Automation.Internal;11using System.Text;12 13namespace Microsoft.PowerShell.Commands.Internal.Format14{15    /// <summary>16    /// Writer class to handle Complex Object formatting.17    /// </summary>18    internal sealed class ComplexWriter19    {20        /// <summary>21        /// Initialization method to be called before any other operation.22        /// </summary>23        /// <param name="lineOutput">LineOutput interfaces to write to.</param>24        /// <param name="numberOfTextColumns">Number of columns used to write out.</param>25        internal void Initialize(LineOutput lineOutput, int numberOfTextColumns)26        {27            _lo = lineOutput;28            _textColumns = numberOfTextColumns;29        }30 31        /// <summary>32        /// Writes a string.33        /// </summary>34        /// <param name="s"></param>35        internal void WriteString(string s)36        {37            _indentationManager.Clear();38 39            AddToBuffer(s);40 41            WriteToScreen();42        }43 44        /// <summary>45        /// It interprets a list of format value tokens and outputs it.46        /// </summary>47        /// <param name="formatValueList">List of FormatValue tokens to interpret.</param>48        internal void WriteObject(List<FormatValue> formatValueList)49        {50            // we always start with no indentation51            _indentationManager.Clear();52 53            foreach (FormatEntry fe in formatValueList)54            {55                // operate on each directive inside the list,56                // carrying the indentation from invocation to invocation57                GenerateFormatEntryDisplay(fe, 0);58            }59            // make sure that, if we have pending text in the buffer it gets flushed60            WriteToScreen();61        }62 63        /// <summary>64        /// Operate on a single entry.65        /// </summary>66        /// <param name="fe">Entry to process.</param>67        /// <param name="currentDepth">Current depth of recursion.</param>68        private void GenerateFormatEntryDisplay(FormatEntry fe, int currentDepth)69        {70            foreach (object obj in fe.formatValueList)71            {72                if (obj is FormatEntry feChild)73                {74                    if (currentDepth < maxRecursionDepth)75                    {76                        if (feChild.frameInfo != null)77                        {78                            // if we have frame information, we need to push it on the79                            // indentation stack80                            using (_indentationManager.StackFrame(feChild.frameInfo))81                            {82                                GenerateFormatEntryDisplay(feChild, currentDepth + 1);83                            }84                        }85                        else86                        {87                            // no need here of activating an indentation stack frame88                            GenerateFormatEntryDisplay(feChild, currentDepth + 1);89                        }90                    }91 92                    continue;93                }94 95                if (obj is FormatNewLine)96                {97                    this.WriteToScreen();98                    continue;99                }100 101                if (obj is FormatTextField ftf)102                {103                    this.AddToBuffer(ftf.text);104                    continue;105                }106 107                if (obj is FormatPropertyField fpf)108                {109                    this.AddToBuffer(fpf.propertyValue);110                }111            }112        }113 114        /// <summary>115        /// Add a string to the current buffer, waiting for a FlushBuffer()116        /// </summary>117        /// <param name="s">String to add to buffer.</param>118        private void AddToBuffer(string s)119        {120            _stringBuffer.Append(s);121        }122 123        /// <summary>124        /// Write to the output interface.125        /// </summary>126        private void WriteToScreen()127        {128            int leftIndentation = _indentationManager.LeftIndentation;129            int rightIndentation = _indentationManager.RightIndentation;130            int firstLineIndentation = _indentationManager.FirstLineIndentation;131 132            // VALIDITY CHECKS:133 134            // check the useful ("active") width135            int usefulWidth = _textColumns - rightIndentation - leftIndentation;136            if (usefulWidth <= 0)137            {138                // fatal error, there is nothing to write to the device139                // just clear the buffer and return140                _stringBuffer = new StringBuilder();141            }142 143            // check indentation or hanging is not larger than the active width144            int indentationAbsoluteValue = (firstLineIndentation > 0) ? firstLineIndentation : -firstLineIndentation;145            if (indentationAbsoluteValue >= usefulWidth)146            {147                // value too big, we reset it to zero148                firstLineIndentation = 0;149            }150 151            // compute the first line indentation or hanging152            int firstLineWidth = _textColumns - rightIndentation - leftIndentation;153            int followingLinesWidth = firstLineWidth;154 155            if (firstLineIndentation >= 0)156            {157                // the first line has an indentation158                firstLineWidth -= firstLineIndentation;159            }160            else161            {162                // the first line is hanging163                followingLinesWidth += firstLineIndentation;164            }165 166            // error checking on invalid values167 168            // generate the lines using the computed widths169            StringCollection sc = StringManipulationHelper.GenerateLines(_lo.DisplayCells, _stringBuffer.ToString(),170                                        firstLineWidth, followingLinesWidth);171 172            // compute padding173            int firstLinePadding = leftIndentation;174            int followingLinesPadding = leftIndentation;175            if (firstLineIndentation >= 0)176            {177                // the first line has an indentation178                firstLinePadding += firstLineIndentation;179            }180            else181            {182                // the first line is hanging183                followingLinesPadding -= firstLineIndentation;184            }185 186            // now write the lines on the screen187            bool firstLine = true;188            foreach (string s in sc)189            {190                if (firstLine)191                {192                    firstLine = false;193                    _lo.WriteLine(StringManipulationHelper.PadLeft(s, firstLinePadding));194                }195                else196                {197                    _lo.WriteLine(StringManipulationHelper.PadLeft(s, followingLinesPadding));198                }199            }200 201            _stringBuffer = new StringBuilder();202        }203 204        /// <summary>205        /// Helper object to manage the frame-based indentation and margins.206        /// </summary>207        private readonly IndentationManager _indentationManager = new IndentationManager();208 209        /// <summary>210        /// Buffer to accumulate partially constructed text.211        /// </summary>212        private StringBuilder _stringBuffer = new StringBuilder();213 214        /// <summary>215        /// Interface to write to.216        /// </summary>217        private LineOutput _lo;218 219        /// <summary>220        /// Number of columns for the output device.221        /// </summary>222        private int _textColumns;223 224        private const int maxRecursionDepth = 50;225    }226 227    internal sealed class IndentationManager228    {229        private sealed class IndentationStackFrame : IDisposable230        {231            internal IndentationStackFrame(IndentationManager mgr)232            {233                _mgr = mgr;234            }235 236            public void Dispose()237            {238                _mgr?.RemoveStackFrame();239            }240 241            private readonly IndentationManager _mgr;242        }243 244        internal void Clear()245        {246            _frameInfoStack.Clear();247        }248 249        internal IDisposable StackFrame(FrameInfo frameInfo)250        {251            IndentationStackFrame frame = new IndentationStackFrame(this);252            _frameInfoStack.Push(frameInfo);253            return frame;254        }255 256        private void RemoveStackFrame()257        {258            _frameInfoStack.Pop();259        }260 261        internal int RightIndentation262        {263            get264            {265                return ComputeRightIndentation();266            }267        }268 269        internal int LeftIndentation270        {271            get272            {273                return ComputeLeftIndentation();274            }275        }276 277        internal int FirstLineIndentation278        {279            get280            {281                if (_frameInfoStack.Count == 0)282                    return 0;283                return _frameInfoStack.Peek().firstLine;284            }285        }286 287        private int ComputeRightIndentation()288        {289            int val = 0;290            foreach (FrameInfo fi in _frameInfoStack)291            {292                val += fi.rightIndentation;293            }294 295            return val;296        }297 298        private int ComputeLeftIndentation()299        {300            int val = 0;301            foreach (FrameInfo fi in _frameInfoStack)302            {303                val += fi.leftIndentation;304            }305 306            return val;307        }308 309        private readonly Stack<FrameInfo> _frameInfoStack = new Stack<FrameInfo>();310    }311 312    /// <summary>313    /// Result of GetWords.314    /// </summary>315    internal struct GetWordsResult316    {317        internal string Word;318        internal string Delim;319        internal bool VtResetAdded;320    }321 322    /// <summary>323    /// Collection of helper functions for string formatting.324    /// </summary>325    internal sealed class StringManipulationHelper326    {327        private const char SoftHyphen = '\u00AD';328        private const char HardHyphen = '\u2011';329        private const char NonBreakingSpace = '\u00A0';330 331        private static readonly Collection<string> s_cultureCollection = new Collection<string>();332 333        static StringManipulationHelper()334        {335            s_cultureCollection.Add("en");        // English336            s_cultureCollection.Add("fr");        // French337            s_cultureCollection.Add("de");        // German338            s_cultureCollection.Add("it");        // Italian339            s_cultureCollection.Add("pt");        // Portuguese340            s_cultureCollection.Add("es");        // Spanish341        }342 343        /// <summary>344        /// Breaks a string into a collection of words345        /// TODO: we might be able to improve this function in the future346        /// so that we do not break paths etc.347        /// </summary>348        /// <param name="s">Input string.</param>349        /// <returns>A collection of words.</returns>350        private static IEnumerable<GetWordsResult> GetWords(string s)351        {352            StringBuilder sb = new StringBuilder();353            StringBuilder vtSeqs = null;354            Dictionary<int, int> vtRanges = null;355 356            var valueStrDec = new ValueStringDecorated(s);357            if (valueStrDec.IsDecorated)358            {359                vtSeqs = new StringBuilder();360                vtRanges = valueStrDec.EscapeSequenceRanges;361            }362 363            bool wordHasVtSeqs = false;364            for (int i = 0; i < s.Length; i++)365            {366                if (vtRanges?.TryGetValue(i, out int len) == true)367                {368                    var vtSpan = s.AsSpan(i, len);369                    sb.Append(vtSpan);370 371                    if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))372                    {373                        // The Reset sequence will void all previous VT sequences.374                        vtSeqs.Clear();375                        wordHasVtSeqs = false;376                    }377                    else378                    {379                        vtSeqs.Append(vtSpan);380                        wordHasVtSeqs = true;381                    }382 383                    i += len - 1;384                    continue;385                }386 387                string delimiter = null;388                if (s[i] is ' ' or '\t' or SoftHyphen)389                {390                    // Soft hyphen = \u00AD - Should break, and add a hyphen if needed.391                    // If not needed for a break, hyphen should be absent.392                    delimiter = new string(s[i], 1);393                }394                else if (s[i] is HardHyphen or NonBreakingSpace)395                {396                    // Non-breaking space = \u00A0 - ideally shouldn't wrap.397                    // Hard hyphen = \u2011 - Should not break.398                    delimiter = string.Empty;399                }400 401                if (delimiter is not null)402                {403                    bool vtResetAdded = false;404                    if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))405                    {406                        vtResetAdded = true;407                        sb.Append(PSStyle.Instance.Reset);408                    }409 410                    var result = new GetWordsResult()411                    {412                        Word = sb.ToString(),413                        Delim = delimiter,414                        VtResetAdded = vtResetAdded415                    };416 417                    sb.Clear().Append(vtSeqs);418                    yield return result;419                }420                else421                {422                    sb.Append(s[i]);423                }424            }425 426            if (wordHasVtSeqs)427            {428                if (sb.Length == vtSeqs.Length)429                {430                    // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with a word delimiter.431                    // For a word that contains VT sequence only, it's the same as an empty string to the formatting system,432                    // because nothing will actually be rendered.433                    // So, we use an empty string in this case to avoid unneeded string allocations.434                    sb.Clear();435                }436                else if (!sb.EndsWith(PSStyle.Instance.Reset))437                {438                    sb.Append(PSStyle.Instance.Reset);439                }440            }441 442            yield return new GetWordsResult() { Word = sb.ToString(), Delim = string.Empty };443        }444 445        internal static StringCollection GenerateLines(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen)446        {447            if (s_cultureCollection.Contains(CultureInfo.CurrentCulture.TwoLetterISOLanguageName))448            {449                return GenerateLinesWithWordWrap(displayCells, val, firstLineLen, followingLinesLen);450            }451            else452            {453                return GenerateLinesWithoutWordWrap(displayCells, val, firstLineLen, followingLinesLen);454            }455        }456 457        private static StringCollection GenerateLinesWithoutWordWrap(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen)458        {459            StringCollection retVal = new StringCollection();460 461            if (string.IsNullOrEmpty(val))462            {463                // if null or empty, just add and we are done464                retVal.Add(val);465                return retVal;466            }467 468            // break string on newlines and process each line separately469            List<string> lines = SplitLines(val);470 471            for (int k = 0; k < lines.Count; k++)472            {473                string currentLine = lines[k];474 475                if (currentLine == null || displayCells.Length(currentLine) <= firstLineLen)476                {477                    // we do not need to split further, just add478                    retVal.Add(currentLine);479                    continue;480                }481 482                // the string does not fit, so we have to wrap around on multiple lines483                // for each of these lines in the string, the first line will have484                // a (potentially) different length (indentation or hanging)485 486                // for each line, start a new state487                SplitLinesAccumulator accumulator = new SplitLinesAccumulator(retVal, firstLineLen, followingLinesLen);488 489                int offset = 0; // offset into the line we are splitting490 491                while (offset < currentLine.Length)492                {493                    // acquire the current active display line length (it can very from call to call)494                    int currentDisplayLen = accumulator.ActiveLen;495 496                    // determine if the current tail would fit or not497 498                    // for the remaining part of the string, determine its display cell count499                    int currentCellsToFit = displayCells.Length(currentLine, offset);500 501                    // determine if we fit into the line502                    int excessCells = currentCellsToFit - currentDisplayLen;503 504                    if (excessCells > 0)505                    {506                        // we are not at the end of the string, select a sub string507                        // that would fit in the remaining display length508                        int charactersToAdd = displayCells.TruncateTail(currentLine, offset, currentDisplayLen);509 510                        if (charactersToAdd <= 0)511                        {512                            // corner case: we have a two cell character and the current513                            // display length is one.514                            // add a single cell arbitrary character instead of the original515                            // one and keep going516                            charactersToAdd = 1;517                            accumulator.AddLine("?");518                        }519                        else520                        {521                            // of the given length, add it to the accumulator522                            accumulator.AddLine(currentLine.VtSubstring(offset, charactersToAdd));523                        }524 525                        // increase the offset by the # of characters added526                        offset += charactersToAdd;527                    }528                    else529                    {530                        // we reached the last (partial) line, we add it all531                        accumulator.AddLine(currentLine.VtSubstring(offset));532                        break;533                    }534                }535            }536 537            return retVal;538        }539 540        private sealed class SplitLinesAccumulator541        {542            internal SplitLinesAccumulator(StringCollection retVal, int firstLineLen, int followingLinesLen)543            {544                _retVal = retVal;545                _firstLineLen = firstLineLen;546                _followingLinesLen = followingLinesLen;547            }548 549            internal void AddLine(string s)550            {551                if (!_addedFirstLine)552                {553                    _addedFirstLine = true;554                }555 556                _retVal.Add(s);557            }558 559            internal int ActiveLen560            {561                get562                {563                    if (_addedFirstLine)564                        return _followingLinesLen;565                    return _firstLineLen;566                }567            }568 569            private readonly StringCollection _retVal;570            private bool _addedFirstLine;571            private readonly int _firstLineLen;572            private readonly int _followingLinesLen;573        }574 575        private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen)576        {577            StringCollection retVal = new StringCollection();578 579            if (string.IsNullOrEmpty(val))580            {581                // if null or empty, just add and we are done582                retVal.Add(val);583                return retVal;584            }585 586            // break string on newlines and process each line separately587            List<string> lines = SplitLines(val);588 589            for (int k = 0; k < lines.Count; k++)590            {591                if (lines[k] == null || displayCells.Length(lines[k]) <= firstLineLen)592                {593                    // we do not need to split further, just add594                    retVal.Add(lines[k]);595                    continue;596                }597 598                int spacesLeft = firstLineLen;599                int lineWidth = firstLineLen;600                bool firstLine = true;601                StringBuilder singleLine = new StringBuilder();602                string resetStr = PSStyle.Instance.Reset;603 604                foreach (GetWordsResult word in GetWords(lines[k]))605                {606                    string wordToAdd = word.Word;607                    string suffix = null;608 609                    // Handle soft hyphen610                    if (word.Delim.Length == 1 && word.Delim[0] is SoftHyphen)611                    {612                        int wordWidthWithHyphen = displayCells.Length(wordToAdd) + displayCells.Length(SoftHyphen);613 614                        // Add hyphen only if necessary615                        if (wordWidthWithHyphen == spacesLeft)616                        {617                            suffix = "-";618                        }619                    }620                    else if (!string.IsNullOrEmpty(word.Delim))621                    {622                        suffix = word.Delim;623                    }624 625                    if (suffix is not null)626                    {627                        wordToAdd = word.VtResetAdded628                            ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix)629                            : wordToAdd + suffix;630                    }631 632                    int wordWidth = displayCells.Length(wordToAdd);633 634                    // Handle zero width635                    if (lineWidth == 0)636                    {637                        if (firstLine)638                        {639                            firstLine = false;640                            lineWidth = followingLinesLen;641                        }642 643                        if (lineWidth == 0)644                        {645                            break;646                        }647 648                        spacesLeft = lineWidth;649                    }650 651                    // Word is wider than a single line652                    if (wordWidth > lineWidth)653                    {654                        Dictionary<int, int> vtRanges = null;655                        StringBuilder vtSeqs = null;656 657                        var valueStrDec = new ValueStringDecorated(wordToAdd);658                        if (valueStrDec.IsDecorated)659                        {660                            vtSeqs = new StringBuilder();661                            vtRanges = valueStrDec.EscapeSequenceRanges;662                        }663 664                        bool hasEscSeqs = false;665                        for (int i = 0; i < wordToAdd.Length; i++)666                        {667                            if (vtRanges?.TryGetValue(i, out int len) == true)668                            {669                                var vtSpan = wordToAdd.AsSpan(i, len);670                                singleLine.Append(vtSpan);671                                vtSeqs.Append(vtSpan);672 673                                hasEscSeqs = true;674                                i += len - 1;675                                continue;676                            }677 678                            char charToAdd = wordToAdd[i];679                            int charWidth = displayCells.Length(charToAdd);680 681                            // Corner case: we have a two cell character and the current display length is one.682                            // Add a single cell arbitrary character instead of the original one and keep going.683                            if (charWidth > lineWidth)684                            {685                                charToAdd = '?';686                                charWidth = 1;687                            }688 689                            if (charWidth > spacesLeft)690                            {691                                if (hasEscSeqs && !singleLine.EndsWith(resetStr))692                                {693                                    singleLine.Append(resetStr);694                                }695 696                                retVal.Add(singleLine.ToString());697                                singleLine.Clear().Append(vtSeqs).Append(charToAdd);698 699                                if (firstLine)700                                {701                                    firstLine = false;702                                    lineWidth = followingLinesLen;703                                }704 705                                spacesLeft = lineWidth - charWidth;706                            }707                            else708                            {709                                singleLine.Append(charToAdd);710                                spacesLeft -= charWidth;711                            }712                        }713                    }714                    else715                    {716                        if (wordWidth > spacesLeft)717                        {718                            retVal.Add(singleLine.ToString());719                            singleLine.Clear().Append(wordToAdd);720 721                            if (firstLine)722                            {723                                firstLine = false;724                                lineWidth = followingLinesLen;725                            }726 727                            spacesLeft = lineWidth - wordWidth;728                        }729                        else730                        {731                            singleLine.Append(wordToAdd);732                            spacesLeft -= wordWidth;733                        }734                    }735                }736 737                retVal.Add(singleLine.ToString());738            }739 740            return retVal;741        }742 743        /// <summary>744        /// Split a multiline string into an array of strings745        /// by honoring both \n and \r\n.746        /// </summary>747        /// <param name="s">String to split.</param>748        /// <returns>String array with the values.</returns>749        internal static List<string> SplitLines(string s)750        {751            if (string.IsNullOrEmpty(s) || !s.Contains('\n'))752            {753                return new List<string>(capacity: 1) { s?.Replace("\r", string.Empty) };754            }755 756            StringBuilder sb = new StringBuilder();757            List<string> list = new List<string>();758 759            StringBuilder vtSeqs = null;760            Dictionary<int, int> vtRanges = null;761 762            var valueStrDec = new ValueStringDecorated(s);763            if (valueStrDec.IsDecorated)764            {765                vtSeqs = new StringBuilder();766                vtRanges = valueStrDec.EscapeSequenceRanges;767            }768 769            bool hasVtSeqs = false;770            for (int i = 0; i < s.Length; i++)771            {772                if (vtRanges?.TryGetValue(i, out int len) == true)773                {774                    var vtSpan = s.AsSpan(i, len);775                    sb.Append(vtSpan);776 777                    if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))778                    {779                        // The Reset sequence will void all previous VT sequences.780                        vtSeqs.Clear();781                        hasVtSeqs = false;782                    }783                    else784                    {785                        vtSeqs.Append(vtSpan);786                        hasVtSeqs = true;787                    }788 789                    i += len - 1;790                    continue;791                }792 793                char c = s[i];794                if (c == '\n')795                {796                    if (hasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))797                    {798                        sb.Append(PSStyle.Instance.Reset);799                    }800 801                    list.Add(sb.ToString());802                    sb.Clear().Append(vtSeqs);803                }804                else if (c != '\r')805                {806                    sb.Append(c);807                }808            }809 810            if (hasVtSeqs)811            {812                if (sb.Length == vtSeqs.Length)813                {814                    // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with '\n'.815                    // For a sub-string that contains VT sequence only, it's the same as an empty string to the formatting816                    // system, because nothing will actually be rendered.817                    // So, we use an empty string in this case to avoid unneeded string allocations.818                    sb.Clear();819                }820                else if (!sb.EndsWith(PSStyle.Instance.Reset))821                {822                    sb.Append(PSStyle.Instance.Reset);823                }824            }825 826            list.Add(sb.ToString());827            return list;828        }829 830        internal static string TruncateAtNewLine(string s)831        {832            if (string.IsNullOrEmpty(s))833            {834                return string.Empty;835            }836 837            int lineBreak = s.AsSpan().IndexOfAny('\n', '\r');838 839            if (lineBreak < 0)840            {841                return s;842            }843 844            return s.Substring(0, lineBreak) + PSObjectHelper.Ellipsis;845        }846 847        internal static string PadLeft(string val, int count)848        {849            return StringUtil.Padding(count) + val;850        }851    }852}853