mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
274 lines
11 KiB
C#
274 lines
11 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Management.Automation.Host;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation.Internal
|
|
{
|
|
internal static class StringUtil
|
|
{
|
|
internal static string Format(string format, object arg0)
|
|
=> string.Format(CultureInfo.CurrentCulture, format, arg0);
|
|
|
|
internal static string Format(string format, object arg0, object arg1)
|
|
=> string.Format(CultureInfo.CurrentCulture, format, arg0, arg1);
|
|
|
|
internal static string Format(string format, object arg0, object arg1, object arg2)
|
|
=> string.Format(CultureInfo.CurrentCulture, format, arg0, arg1, arg2);
|
|
|
|
internal static string Format(string format, params object[] args)
|
|
=> string.Format(CultureInfo.CurrentCulture, format, args);
|
|
|
|
internal static string TruncateToBufferCellWidth(PSHostRawUserInterface rawUI, string toTruncate, int maxWidthInBufferCells)
|
|
{
|
|
Dbg.Assert(rawUI != null, "need a reference");
|
|
Dbg.Assert(maxWidthInBufferCells >= 0, "maxWidthInBufferCells must be positive");
|
|
|
|
string result;
|
|
int i = Math.Min(toTruncate.Length, maxWidthInBufferCells);
|
|
|
|
while (true)
|
|
{
|
|
result = toTruncate.Substring(0, i);
|
|
int cellCount = rawUI.LengthInBufferCells(result);
|
|
if (cellCount <= maxWidthInBufferCells)
|
|
{
|
|
// the segment from start..i fits
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// The segment does not fit, back off a tad until it does
|
|
// We need to back off 1 by 1 because there could theoretically
|
|
// be characters taking more 2 buffer cells
|
|
--i;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Typical padding is at most a screen's width, any more than that and we won't bother caching.
|
|
private const int IndentCacheMax = 120;
|
|
|
|
private static readonly string[] s_indentCache = new string[IndentCacheMax];
|
|
|
|
internal static string Padding(int countOfSpaces)
|
|
{
|
|
if (countOfSpaces >= IndentCacheMax)
|
|
return new string(' ', countOfSpaces);
|
|
|
|
var result = s_indentCache[countOfSpaces];
|
|
|
|
if (result == null)
|
|
{
|
|
Interlocked.CompareExchange(ref s_indentCache[countOfSpaces], new string(' ', countOfSpaces), null);
|
|
result = s_indentCache[countOfSpaces];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private const int DashCacheMax = 120;
|
|
|
|
private static readonly string[] s_dashCache = new string[DashCacheMax];
|
|
|
|
internal static string DashPadding(int count)
|
|
{
|
|
if (count >= DashCacheMax)
|
|
return new string('-', count);
|
|
|
|
var result = s_dashCache[count];
|
|
|
|
if (result == null)
|
|
{
|
|
Interlocked.CompareExchange(ref s_dashCache[count], new string('-', count), null);
|
|
result = s_dashCache[count];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Substring implementation that takes into account the VT escape sequences.
|
|
/// </summary>
|
|
/// <param name="str">String that may contain VT escape sequences.</param>
|
|
/// <param name="startOffset">
|
|
/// 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>The requested substring.</returns>
|
|
internal static string VtSubstring(this string str, int startOffset)
|
|
{
|
|
return VtSubstring(str, startOffset, int.MaxValue, prependStr: null, appendStr: null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Substring implementation that takes into account the VT escape sequences.
|
|
/// </summary>
|
|
/// <param name="str">String that may contain VT escape sequences.</param>
|
|
/// <param name="startOffset">
|
|
/// 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="length">Number of non-escape-sequence characters to be included in the substring.</param>
|
|
/// <returns>The requested substring.</returns>
|
|
internal static string VtSubstring(this string str, int startOffset, int length)
|
|
{
|
|
return VtSubstring(str, startOffset, length, prependStr: null, appendStr: null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Substring implementation that takes into account the VT escape sequences.
|
|
/// </summary>
|
|
/// <param name="str">String that may contain VT escape sequences.</param>
|
|
/// <param name="startOffset">
|
|
/// 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="prependStr">The string to be prepended to the substring.</param>
|
|
/// <param name="appendStr">The string to be appended to the substring.</param>
|
|
/// <returns>The requested substring.</returns>
|
|
internal static string VtSubstring(this string str, int startOffset, string prependStr, string appendStr)
|
|
{
|
|
return VtSubstring(str, startOffset, int.MaxValue, prependStr, appendStr);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Substring implementation that takes into account the VT escape sequences.
|
|
/// </summary>
|
|
/// <param name="str">String that may contain VT escape sequences.</param>
|
|
/// <param name="startOffset">
|
|
/// 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="length">Number of non-escape-sequence characters to be included in the substring.</param>
|
|
/// <param name="prependStr">The string to be prepended to the substring.</param>
|
|
/// <param name="appendStr">The string to be appended to the substring.</param>
|
|
/// <returns>The requested substring.</returns>
|
|
internal static string VtSubstring(this string str, int startOffset, int length, string prependStr, string appendStr)
|
|
{
|
|
var valueStrDec = new ValueStringDecorated(str);
|
|
if (valueStrDec.IsDecorated)
|
|
{
|
|
// Handle strings with VT sequences.
|
|
bool copyStarted = startOffset == 0;
|
|
bool hasEscSeqs = false;
|
|
bool firstNonEscChar = true;
|
|
StringBuilder sb = new(capacity: str.Length);
|
|
Dictionary<int, int> vtRanges = valueStrDec.EscapeSequenceRanges;
|
|
|
|
for (int i = 0, offset = 0; i < str.Length; i++)
|
|
{
|
|
// Keep all leading ANSI escape sequences.
|
|
if (vtRanges.TryGetValue(i, out int len))
|
|
{
|
|
hasEscSeqs = true;
|
|
sb.Append(str.AsSpan(i, len));
|
|
|
|
i += len - 1;
|
|
continue;
|
|
}
|
|
|
|
// OK, now we get a non-escape-sequence character.
|
|
if (copyStarted)
|
|
{
|
|
if (firstNonEscChar)
|
|
{
|
|
// Prepend the string before we copy the first non-escape-sequence character.
|
|
sb.Append(prependStr);
|
|
firstNonEscChar = false;
|
|
}
|
|
|
|
// Copy this character if we've started the copy.
|
|
sb.Append(str[i]);
|
|
|
|
// Increment 'offset' to keep track of number of non-escape-sequence characters we've copied.
|
|
offset++;
|
|
}
|
|
else if (++offset == startOffset)
|
|
{
|
|
// We've skipped enough non-escape-sequence characters, and will be copying the next one.
|
|
copyStarted = true;
|
|
|
|
// Reset 'offset' and from now on use it to track the number of copied non-escape-sequence characters.
|
|
offset = 0;
|
|
continue;
|
|
}
|
|
|
|
// If the number of copied non-escape-sequence characters has reached the specified length, done copying.
|
|
if (copyStarted && offset == length)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hasEscSeqs)
|
|
{
|
|
string resetStr = PSStyle.Instance.Reset;
|
|
bool endsWithReset = sb.EndsWith(resetStr);
|
|
if (endsWithReset)
|
|
{
|
|
// Append the given string before the reset VT sequence.
|
|
sb.Insert(sb.Length - resetStr.Length, appendStr);
|
|
}
|
|
else
|
|
{
|
|
// Append the given string and add the reset VT sequence.
|
|
sb.Append(appendStr).Append(resetStr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.Append(appendStr);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
// Handle strings without VT sequences.
|
|
if (length == int.MaxValue)
|
|
{
|
|
length = str.Length - startOffset;
|
|
}
|
|
|
|
if (prependStr is null && appendStr is null)
|
|
{
|
|
return str.Substring(startOffset, length);
|
|
}
|
|
else
|
|
{
|
|
int capacity = length + prependStr?.Length ?? 0 + appendStr?.Length ?? 0;
|
|
return new StringBuilder(prependStr, capacity)
|
|
.Append(str, startOffset, length)
|
|
.Append(appendStr)
|
|
.ToString();
|
|
}
|
|
}
|
|
|
|
internal static bool EndsWith(this StringBuilder sb, string value)
|
|
{
|
|
if (sb.Length < value.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int offset = sb.Length - value.Length;
|
|
for (int i = 0; i < value.Length; i++)
|
|
{
|
|
if (sb[offset + i] != value[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|