Exemplo n.º 1
0
        internal override int Length(string str, int offset)
        {
            if (string.IsNullOrEmpty(str))
            {
                return(0);
            }

            if (offset < 0 || offset >= str.Length)
            {
                throw PSTraceSource.NewArgumentException(nameof(offset));
            }

            try
            {
                var valueStrDec = new ValueStringDecorated(str);
                if (valueStrDec.IsDecorated)
                {
                    str = valueStrDec.ToString(OutputRendering.PlainText);
                }

                int length = 0;
                for (; offset < str.Length; offset++)
                {
                    length += _rawUserInterface.LengthInBufferCells(str[offset]);
                }

                return(length);
            }
            catch
            {
                // thrown when external host rawui is not implemented, in which case
                // we will fallback to the default value.
                return(base.Length(str, offset));
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Truncate from the tail of the string.
        /// </summary>
        /// <param name="str">String that may contain VT escape sequences.</param>
        /// <param name="offset">
        /// When the string doesn't contain VT sequences, it's the starting index.
        /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>
        /// <param name="displayCells">Number of buffer cells to fit in.</param>
        /// <returns>Number of non-escape-sequence characters from head of the string that can fit in the space.</returns>
        internal int TruncateTail(string str, int offset, int displayCells)
        {
            var valueStrDec = new ValueStringDecorated(str);

            if (valueStrDec.IsDecorated)
            {
                str = valueStrDec.ToString(OutputRendering.PlainText);
            }

            return(GetFitLength(str, offset, displayCells, startFromHead: true));
        }
Exemplo n.º 3
0
        /// <summary>
        /// Truncate from the head of the string.
        /// </summary>
        /// <param name="str">String that may contain VT escape sequences.</param>
        /// <param name="displayCells">Number of buffer cells to fit in.</param>
        /// <returns>Number of non-escape-sequence characters from head of the string that should be skipped.</returns>
        internal int TruncateHead(string str, int displayCells)
        {
            var valueStrDec = new ValueStringDecorated(str);

            if (valueStrDec.IsDecorated)
            {
                str = valueStrDec.ToString(OutputRendering.PlainText);
            }

            int tailCount = GetFitLength(str, offset: 0, displayCells, startFromHead: false);

            return(str.Length - tailCount);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Calculate the buffer cell length of the given string.
        /// </summary>
        /// <param name="str">String that may contain VT escape sequences.</param>
        /// <param name="offset">
        /// When the string doesn't contain VT sequences, it's the starting index.
        /// When the string contains VT sequences, it means starting from the 'n-th' char that doesn't belong to a escape sequence.</param>
        /// <returns>Number of buffer cells the string needs to take.</returns>
        internal virtual int Length(string str, int offset)
        {
            if (string.IsNullOrEmpty(str))
            {
                return(0);
            }

            var valueStrDec = new ValueStringDecorated(str);

            if (valueStrDec.IsDecorated)
            {
                str = valueStrDec.ToString(OutputRendering.PlainText);
            }

            int length = 0;

            for (; offset < str.Length; offset++)
            {
                length += CharLengthInBufferCells(str[offset]);
            }

            return(length);
        }
Exemplo n.º 5
0
        /// <summary>
        /// Split a multiline string into an array of strings
        /// by honoring both \n and \r\n.
        /// </summary>
        /// <param name="s">String to split.</param>
        /// <returns>String array with the values.</returns>
        internal static List <string> SplitLines(string s)
        {
            if (string.IsNullOrEmpty(s) || !s.Contains('\n'))
            {
                return(new List <string>(capacity: 1)
                {
                    s?.Replace("\r", string.Empty)
                });
            }

            StringBuilder sb   = new StringBuilder();
            List <string> list = new List <string>();

            StringBuilder         vtSeqs   = null;
            Dictionary <int, int> vtRanges = null;

            var valueStrDec = new ValueStringDecorated(s);

            if (valueStrDec.IsDecorated)
            {
                vtSeqs   = new StringBuilder();
                vtRanges = valueStrDec.EscapeSequenceRanges;
            }

            bool hasVtSeqs = false;

            for (int i = 0; i < s.Length; i++)
            {
                if (vtRanges?.TryGetValue(i, out int len) == true)
                {
                    var vtSpan = s.AsSpan(i, len);
                    sb.Append(vtSpan);
                    vtSeqs.Append(vtSpan);

                    hasVtSeqs = true;
                    i        += len - 1;
                    continue;
                }

                char c = s[i];
                if (c == '\n')
                {
                    if (hasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))
                    {
                        sb.Append(PSStyle.Instance.Reset);
                    }

                    list.Add(sb.ToString());
                    sb.Clear().Append(vtSeqs);
                }
                else if (c != '\r')
                {
                    sb.Append(c);
                }
            }

            if (hasVtSeqs)
            {
                if (sb.Length == vtSeqs.Length)
                {
                    // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with '\n'.
                    // For a sub-string that contains VT sequence only, it's the same as an empty string to the formatting
                    // system, because nothing will actually be rendered.
                    // So, we use an empty string in this case to avoid unneeded string allocations.
                    sb.Clear();
                }
                else if (!sb.EndsWith(PSStyle.Instance.Reset))
                {
                    sb.Append(PSStyle.Instance.Reset);
                }
            }

            list.Add(sb.ToString());
            return(list);
        }
Exemplo n.º 6
0
        private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen)
        {
            StringCollection retVal = new StringCollection();

            if (string.IsNullOrEmpty(val))
            {
                // if null or empty, just add and we are done
                retVal.Add(val);
                return(retVal);
            }

            // break string on newlines and process each line separately
            List <string> lines = SplitLines(val);

            for (int k = 0; k < lines.Count; k++)
            {
                if (lines[k] == null || displayCells.Length(lines[k]) <= firstLineLen)
                {
                    // we do not need to split further, just add
                    retVal.Add(lines[k]);
                    continue;
                }

                int           spacesLeft = firstLineLen;
                int           lineWidth  = firstLineLen;
                bool          firstLine  = true;
                StringBuilder singleLine = new StringBuilder();
                string        resetStr   = PSStyle.Instance.Reset;

                foreach (GetWordsResult word in GetWords(lines[k]))
                {
                    string wordToAdd = word.Word;
                    string suffix    = null;

                    // Handle soft hyphen
                    if (word.Delim.Length == 1 && word.Delim[0] == s_softHyphen)
                    {
                        int wordWidthWithHyphen = displayCells.Length(wordToAdd) + displayCells.Length(s_softHyphen);

                        // Add hyphen only if necessary
                        if (wordWidthWithHyphen == spacesLeft)
                        {
                            suffix = "-";
                        }
                    }
                    else if (!string.IsNullOrEmpty(word.Delim))
                    {
                        suffix = word.Delim;
                    }

                    if (suffix is not null)
                    {
                        wordToAdd = wordToAdd.EndsWith(resetStr)
                            ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix)
                            : wordToAdd + suffix;
                    }

                    int wordWidth = displayCells.Length(wordToAdd);

                    // Handle zero width
                    if (lineWidth == 0)
                    {
                        if (firstLine)
                        {
                            firstLine = false;
                            lineWidth = followingLinesLen;
                        }

                        if (lineWidth == 0)
                        {
                            break;
                        }

                        spacesLeft = lineWidth;
                    }

                    // Word is wider than a single line
                    if (wordWidth > lineWidth)
                    {
                        Dictionary <int, int> vtRanges = null;
                        StringBuilder         vtSeqs   = null;

                        var valueStrDec = new ValueStringDecorated(wordToAdd);
                        if (valueStrDec.IsDecorated)
                        {
                            vtSeqs   = new StringBuilder();
                            vtRanges = valueStrDec.EscapeSequenceRanges;
                        }

                        bool hasEscSeqs = false;
                        for (int i = 0; i < wordToAdd.Length; i++)
                        {
                            if (vtRanges?.TryGetValue(i, out int len) == true)
                            {
                                var vtSpan = wordToAdd.AsSpan(i, len);
                                singleLine.Append(vtSpan);
                                vtSeqs.Append(vtSpan);

                                hasEscSeqs = true;
                                i         += len - 1;
                                continue;
                            }

                            char charToAdd = wordToAdd[i];
                            int  charWidth = displayCells.Length(charToAdd);

                            // Corner case: we have a two cell character and the current display length is one.
                            // Add a single cell arbitrary character instead of the original one and keep going.
                            if (charWidth > lineWidth)
                            {
                                charToAdd = '?';
                                charWidth = 1;
                            }

                            if (charWidth > spacesLeft)
                            {
                                if (hasEscSeqs && !singleLine.EndsWith(resetStr))
                                {
                                    singleLine.Append(resetStr);
                                }

                                retVal.Add(singleLine.ToString());
                                singleLine.Clear().Append(vtSeqs).Append(charToAdd);

                                if (firstLine)
                                {
                                    firstLine = false;
                                    lineWidth = followingLinesLen;
                                }

                                spacesLeft = lineWidth - charWidth;
                            }
                            else
                            {
                                singleLine.Append(charToAdd);
                                spacesLeft -= charWidth;
                            }
                        }
                    }
                    else
                    {
                        if (wordWidth > spacesLeft)
                        {
                            retVal.Add(singleLine.ToString());
                            singleLine.Clear().Append(wordToAdd);

                            if (firstLine)
                            {
                                firstLine = false;
                                lineWidth = followingLinesLen;
                            }

                            spacesLeft = lineWidth - wordWidth;
                        }
                        else
                        {
                            singleLine.Append(wordToAdd);
                            spacesLeft -= wordWidth;
                        }
                    }
                }

                retVal.Add(singleLine.ToString());
            }

            return(retVal);
        }
Exemplo n.º 7
0
        /// <summary>
        /// Breaks a string into a collection of words
        /// TODO: we might be able to improve this function in the future
        /// so that we do not break paths etc.
        /// </summary>
        /// <param name="s">Input string.</param>
        /// <returns>A collection of words.</returns>
        private static IEnumerable <GetWordsResult> GetWords(string s)
        {
            StringBuilder         sb       = new StringBuilder();
            StringBuilder         vtSeqs   = null;
            Dictionary <int, int> vtRanges = null;

            var valueStrDec = new ValueStringDecorated(s);

            if (valueStrDec.IsDecorated)
            {
                vtSeqs   = new StringBuilder();
                vtRanges = valueStrDec.EscapeSequenceRanges;
            }

            bool wordHasVtSeqs = false;

            for (int i = 0; i < s.Length; i++)
            {
                if (vtRanges?.TryGetValue(i, out int len) == true)
                {
                    var vtSpan = s.AsSpan(i, len);
                    sb.Append(vtSpan);
                    vtSeqs.Append(vtSpan);

                    wordHasVtSeqs = true;
                    i            += len - 1;
                    continue;
                }

                string delimiter = null;
                if (s[i] == ' ' || s[i] == '\t' || s[i] == s_softHyphen)
                {
                    // Soft hyphen = \u00AD - Should break, and add a hyphen if needed.
                    // If not needed for a break, hyphen should be absent.
                    delimiter = new string(s[i], 1);
                }
                else if (s[i] == s_hardHyphen || s[i] == s_nonBreakingSpace)
                {
                    // Non-breaking space = \u00A0 - ideally shouldn't wrap.
                    // Hard hyphen = \u2011 - Should not break.
                    delimiter = string.Empty;
                }

                if (delimiter is not null)
                {
                    if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))
                    {
                        sb.Append(PSStyle.Instance.Reset);
                    }

                    var result = new GetWordsResult()
                    {
                        Word  = sb.ToString(),
                        Delim = delimiter
                    };

                    sb.Clear().Append(vtSeqs);
                    yield return(result);
                }
                else
                {
                    sb.Append(s[i]);
                }
            }

            if (wordHasVtSeqs)
            {
                if (sb.Length == vtSeqs.Length)
                {
                    // This indicates 'sb' only contains all VT sequences, which may happen when the string ends with a word delimiter.
                    // For a word that contains VT sequence only, it's the same as an empty string to the formatting system,
                    // because nothing will actually be rendered.
                    // So, we use an empty string in this case to avoid unneeded string allocations.
                    sb.Clear();
                }
                else if (!sb.EndsWith(PSStyle.Instance.Reset))
                {
                    sb.Append(PSStyle.Instance.Reset);
                }
            }

            yield return(new GetWordsResult()
            {
                Word = sb.ToString(), Delim = string.Empty
            });
        }