Fix formatting to properly handle the Reset VT sequences that appear in the middle of a string (#26424)

This commit is contained in:
Dongbo Wang
2025-11-24 14:38:23 -08:00
committed by GitHub
parent 9ee3c61c26
commit eb2ead439a
3 changed files with 110 additions and 10 deletions
@@ -9,7 +9,6 @@ using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
@@ -317,6 +316,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
internal string Word;
internal string Delim;
internal bool VtResetAdded;
}
/// <summary>
@@ -367,9 +367,19 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
var vtSpan = s.AsSpan(i, len);
sb.Append(vtSpan);
vtSeqs.Append(vtSpan);
wordHasVtSeqs = true;
if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))
{
// The Reset sequence will void all previous VT sequences.
vtSeqs.Clear();
wordHasVtSeqs = false;
}
else
{
vtSeqs.Append(vtSpan);
wordHasVtSeqs = true;
}
i += len - 1;
continue;
}
@@ -390,15 +400,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
if (delimiter is not null)
{
bool vtResetAdded = false;
if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))
{
vtResetAdded = true;
sb.Append(PSStyle.Instance.Reset);
}
var result = new GetWordsResult()
{
Word = sb.ToString(),
Delim = delimiter
Delim = delimiter,
VtResetAdded = vtResetAdded
};
sb.Clear().Append(vtSeqs);
@@ -611,7 +624,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
if (suffix is not null)
{
wordToAdd = wordToAdd.EndsWith(resetStr)
wordToAdd = word.VtResetAdded
? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix)
: wordToAdd + suffix;
}
@@ -760,9 +773,19 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
var vtSpan = s.AsSpan(i, len);
sb.Append(vtSpan);
vtSeqs.Append(vtSpan);
hasVtSeqs = true;
if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))
{
// The Reset sequence will void all previous VT sequences.
vtSeqs.Clear();
hasVtSeqs = false;
}
else
{
vtSeqs.Append(vtSpan);
hasVtSeqs = true;
}
i += len - 1;
continue;
}
@@ -259,7 +259,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
_cachedBuilder.Append(headPadding).Append(str);
}
if (str.Contains(ValueStringDecorated.ESC) && !str.EndsWith(reset))
if (str.Contains(ValueStringDecorated.ESC) && !str.AsSpan().TrimEnd().EndsWith(reset, StringComparison.Ordinal))
{
_cachedBuilder.Append(reset);
}
@@ -488,7 +488,7 @@ Billy Bob… Senior DevOps … 13
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Word wrapping for string with escape sequences" {
It "Word wrapping for string with escape sequences (1)" {
$expected = @"
`e[32;1mLongDescription : `e[0m`e[33mPowerShell `e[0m
`e[33mscripting `e[0m
@@ -501,7 +501,46 @@ Billy Bob… Senior DevOps … 13
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Splitting multi-line string with escape sequences" {
It "Word wrapping for string with escape sequences (2)" {
$expected = @"
`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m
scripting
language
"@
$obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m scripting language" }
$obj | Format-List | Out-String -Width 35 | Out-File $outFile
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Word wrapping for string with escape sequences (3)" {
$expected = @"
`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m
`e[32mscripting `e[0m
`e[32mlanguage`e[0m
"@
$obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting language" }
$obj | Format-List | Out-String -Width 35 | Out-File $outFile
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Word wrapping for string with escape sequences (4)" {
$expected = @"
`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m
`e[32mscripting`e[0m
language
"@
$obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting`e[0m language" }
$obj | Format-List | Out-String -Width 35 | Out-File $outFile
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Splitting multi-line string with escape sequences (1)" {
$expected = @"
`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m
`e[33mconsisting of a command-line shell and the associated scripting language`e[0m
@@ -513,6 +552,30 @@ Billy Bob… Senior DevOps … 13
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Splitting multi-line string with escape sequences (2)" {
$expected = @"
`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m
consisting of a command-line shell and the associated scripting language
"@
$obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`nconsisting of a command-line shell and the associated scripting language" }
$obj | Format-List | Out-File $outFile
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Splitting multi-line string with escape sequences (3)" {
$expected = @"
`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m
`e[32mconsisting of a command-line shell and the associated scripting language`e[0m
"@
$obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`n`e[32mconsisting of a command-line shell and the associated scripting language" }
$obj | Format-List | Out-File $outFile
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Wrapping long word with escape sequences" {
$expected = @"
`e[32;1mb : `e[0m`e[33mC:\repos\PowerShell\src\powershell-w`e[0m
@@ -525,4 +588,18 @@ Billy Bob… Senior DevOps … 13
$text = Get-Content $outFile -Raw
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
It "Format 'MatchInfo' object correctly" {
$expected = @"
`e[32;1mb : `e[0mmouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 `e[0m
32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096
"@
## This string mimics the VT decorated string for a 'MatchInfo' object that matches the word 'mouse'.
$str = "mouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096"
$obj = [pscustomobject] @{ b = $str }
$text = $obj | Format-List | Out-String -Width 150
$text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "")
}
}