diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml index 5218a20cf9..1236a0e278 100644 --- a/.vsts-ci/linux.yml +++ b/.vsts-ci/linux.yml @@ -35,6 +35,7 @@ variables: POWERSHELL_TELEMETRY_OPTOUT: 1 # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + __SuppressAnsiEscapeSequences: 1 resources: - repo: self diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index 1df0bcf611..b048a248bb 100644 --- a/.vsts-ci/mac.yml +++ b/.vsts-ci/mac.yml @@ -37,6 +37,7 @@ variables: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 # Turn off Homebrew analytics HOMEBREW_NO_ANALYTICS: 1 + __SuppressAnsiEscapeSequences: 1 resources: - repo: self diff --git a/.vsts-ci/windows-daily.yml b/.vsts-ci/windows-daily.yml index 65390776c1..7b9408eec7 100644 --- a/.vsts-ci/windows-daily.yml +++ b/.vsts-ci/windows-daily.yml @@ -34,6 +34,7 @@ variables: POWERSHELL_TELEMETRY_OPTOUT: 1 # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + __SuppressAnsiEscapeSequences: 1 resources: - repo: self diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml index 2881a6a4fb..84a9435d04 100644 --- a/.vsts-ci/windows.yml +++ b/.vsts-ci/windows.yml @@ -34,6 +34,7 @@ variables: POWERSHELL_TELEMETRY_OPTOUT: 1 # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + __SuppressAnsiEscapeSequences: 1 resources: - repo: self diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index f1052cbea5..584a3ebe6c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs @@ -256,7 +256,7 @@ namespace Microsoft.PowerShell.Commands } /// - /// Returns the string representation of the match object with the matched line passed + /// Returns the string representation of the match object with the matched line passed /// in as and trims the path to be relative to /// the argument. /// @@ -315,10 +315,10 @@ namespace Microsoft.PowerShell.Commands /// The matched line with matched text inverted. private string EmphasizeLine() { - const string InvertColorsVT100 = "\u001b[7m"; - const string ResetVT100 = "\u001b[0m"; + string invertColorsVT100 = VTUtility.GetEscapeSequence(VTUtility.VT.Inverse); + string resetVT100 = VTUtility.GetEscapeSequence(VTUtility.VT.Reset); - char[] chars = new char[(_matchIndexes.Count * (InvertColorsVT100.Length + ResetVT100.Length)) + Line.Length]; + char[] chars = new char[(_matchIndexes.Count * (invertColorsVT100.Length + resetVT100.Length)) + Line.Length]; int lineIndex = 0; int charsIndex = 0; for (int i = 0; i < _matchIndexes.Count; i++) @@ -329,8 +329,8 @@ namespace Microsoft.PowerShell.Commands lineIndex = _matchIndexes[i]; // Adds opening vt sequence - InvertColorsVT100.CopyTo(0, chars, charsIndex, InvertColorsVT100.Length); - charsIndex += InvertColorsVT100.Length; + invertColorsVT100.CopyTo(0, chars, charsIndex, invertColorsVT100.Length); + charsIndex += invertColorsVT100.Length; // Adds characters being emphasized Line.CopyTo(lineIndex, chars, charsIndex, _matchLengths[i]); @@ -338,8 +338,8 @@ namespace Microsoft.PowerShell.Commands charsIndex += _matchLengths[i]; // Adds closing vt sequence - ResetVT100.CopyTo(0, chars, charsIndex, ResetVT100.Length); - charsIndex += ResetVT100.Length; + resetVT100.CopyTo(0, chars, charsIndex, resetVT100.Length); + charsIndex += resetVT100.Length; } // Adds remaining characters in line @@ -1294,8 +1294,8 @@ namespace Microsoft.PowerShell.Commands private int _postContext = 0; // When we are in Raw mode or pre- and postcontext are zero, use the _noContextTracker, since we will not be needing trackedLines. - private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _postContext == 0)) - ? _noContextTracker + private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _postContext == 0)) + ? _noContextTracker : new ContextTracker(_preContext, _postContext); // This context tracker is only used for strings which are piped @@ -1322,6 +1322,12 @@ namespace Microsoft.PowerShell.Commands /// protected override void BeginProcessing() { + string suppressVt = Environment.GetEnvironmentVariable("__SuppressAnsiEscapeSequences"); + if (!string.IsNullOrEmpty(suppressVt)) + { + NoEmphasis = true; + } + if (!SimpleMatch) { RegexOptions regexOptions = CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index 4bfecc19da..e8c2f2e7bd 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -741,35 +741,18 @@ namespace System.Management.Automation.Runspaces $maxDepth = 10 $ellipsis = ""`u{2026}"" $resetColor = '' - if ($Host.UI.SupportsVirtualTerminal) { - $resetColor = ""`e[0m"" + if ($Host.UI.SupportsVirtualTerminal -and !(Test-Path env:__SuppressAnsiEscapeSequences)) { + $resetColor = [System.Management.Automation.VTUtility]::GetEscapeSequence( + [System.Management.Automation.VTUtility+VT]::Reset + ) } - function Get-VT100Color([string] $color) { - if (! $Host.UI.SupportsVirtualTerminal) { + function Get-VT100Color([ConsoleColor] $color) { + if (!$Host.UI.SupportsVirtualTerminal -or (Test-Path env:__SuppressAnsiEscapeSequences)) { return '' } - $colors = @{ - 'Black' = ""`e[2;30m"" - 'DarkRed' = ""`e[2;31m"" - 'DarkGreen' = ""`e[2;32m"" - 'DarkYellow' = ""`e[2;33m"" - 'DarkBlue' = ""`e[2;34m"" - 'DarkMagenta' = ""`e[2;35m"" - 'DarkCyan' = ""`e[2;36m"" - 'Gray' = ""`e[2;37m"" - 'DarkGray' = ""`e[1;30m"" - 'Red' = ""`e[1;31m"" - 'Green' = ""`e[1;32m"" - 'Yellow' = ""`e[1;33m"" - 'Blue' = ""`e[1;34m"" - 'Magenta' = ""`e[1;35m"" - 'Cyan' = ""`e[1;36m"" - 'White' = ""`e[1;37m"" - } - - return $colors[$color] + return [System.Management.Automation.VTUtility]::GetEscapeSequence($color) } function Show-ErrorRecord($obj, [int]$indent = 0, [int]$depth = 1) { @@ -981,35 +964,18 @@ namespace System.Management.Automation.Runspaces Set-StrictMode -Off $resetColor = '' - if ($Host.UI.SupportsVirtualTerminal) { - $resetColor = ""`e[0m"" + if ($Host.UI.SupportsVirtualTerminal -and !(Test-Path env:__SuppressAnsiEscapeSequences)) { + $resetColor = [System.Management.Automation.VTUtility]::GetEscapeSequence( + [System.Management.Automation.VTUtility+VT]::Reset + ) } - function Get-VT100Color([string] $color) { - if (! $Host.UI.SupportsVirtualTerminal) { + function Get-VT100Color([ConsoleColor] $color) { + if (!$Host.UI.SupportsVirtualTerminal -or (Test-Path env:__SuppressAnsiEscapeSequences)) { return '' } - $colors = @{ - 'Black' = ""`e[2;30m"" - 'DarkRed' = ""`e[2;31m"" - 'DarkGreen' = ""`e[2;32m"" - 'DarkYellow' = ""`e[2;33m"" - 'DarkBlue' = ""`e[2;34m"" - 'DarkMagenta' = ""`e[2;35m"" - 'DarkCyan' = ""`e[2;36m"" - 'Gray' = ""`e[2;37m"" - 'DarkGray' = ""`e[1;30m"" - 'Red' = ""`e[1;31m"" - 'Green' = ""`e[1;32m"" - 'Yellow' = ""`e[1;33m"" - 'Blue' = ""`e[1;34m"" - 'Magenta' = ""`e[1;35m"" - 'Cyan' = ""`e[1;36m"" - 'White' = ""`e[1;37m"" - } - - return $colors[$color] + return [System.Management.Automation.VTUtility]::GetEscapeSequence($color) } # return length of string sans VT100 codes diff --git a/src/System.Management.Automation/utils/VTUtils.cs b/src/System.Management.Automation/utils/VTUtils.cs new file mode 100644 index 0000000000..2ff110dd42 --- /dev/null +++ b/src/System.Management.Automation/utils/VTUtils.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace System.Management.Automation +{ + /// + /// Class to help with VT escape sequences. + /// + public sealed class VTUtility + { + /// + /// Available VT escape codes other than colors. + /// + public enum VT + { + /// Reset the text style. + Reset, + + /// Invert the foreground and background colors. + Inverse + } + + private static readonly Dictionary ConsoleColors = new Dictionary + { + { ConsoleColor.Black, "\x1b[2;30m" }, + { ConsoleColor.Gray, "\x1b[2;37m" }, + { ConsoleColor.Red, "\x1b[1;31m" }, + { ConsoleColor.Green, "\x1b[1;32m" }, + { ConsoleColor.Yellow, "\x1b[1;33m" }, + { ConsoleColor.Blue, "\x1b[1;34m" }, + { ConsoleColor.Magenta, "\x1b[1;35m" }, + { ConsoleColor.Cyan, "\x1b[1;36m" }, + { ConsoleColor.White, "\x1b[1;37m" }, + { ConsoleColor.DarkRed, "\x1b[2;31m" }, + { ConsoleColor.DarkGreen, "\x1b[2;32m" }, + { ConsoleColor.DarkYellow, "\x1b[2;33m" }, + { ConsoleColor.DarkBlue, "\x1b[2;34m" }, + { ConsoleColor.DarkMagenta, "\x1b[2;35m" }, + { ConsoleColor.DarkCyan, "\x1b[2;36m" }, + { ConsoleColor.DarkGray, "\x1b[1;30m" }, + }; + + private static readonly Dictionary VTCodes = new Dictionary + { + { VT.Reset, "\x1b[0m" }, + { VT.Inverse, "\x1b[7m" } + }; + + /// + /// Return the VT escape sequence for a ConsoleColor. + /// + /// + /// The ConsoleColor to return the equivalent VT escape sequence. + /// + /// + /// The requested VT escape sequence. + /// + public static string GetEscapeSequence(ConsoleColor color) + { + string value = string.Empty; + ConsoleColors.TryGetValue(color, out value); + return value; + } + + /// + /// Return the VT escape sequence for a supported VT enum value. + /// + /// + /// The VT code to return the VT escape sequence. + /// + /// + /// The requested VT escape sequence. + /// + public static string GetEscapeSequence(VT vt) + { + string value = string.Empty; + VTCodes.TryGetValue(vt, out value); + return value; + } + } +} diff --git a/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 b/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 new file mode 100644 index 0000000000..c46674ff3a --- /dev/null +++ b/test/powershell/Language/Scripting/SuppressAnsiEscapeSequence.Tests.ps1 @@ -0,0 +1,89 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe '$env:__SuppressAnsiEscapeSequences tests' -Tag CI { + BeforeAll { + $originalSuppressPref = $env:__SuppressAnsiEscapeSequences + } + + AfterAll { + $env:__SuppressAnsiEscapeSequences = $originalSuppressPref + } + + + Context 'Allow Escape Sequences' { + BeforeAll { + Remove-Item env:__SuppressAnsiEscapeSequences -ErrorAction Ignore + } + + It 'Select-String emits VT' { + "select this string" | Select-String 'this' | Out-String |Should -BeLikeExactly "*`e*" + } + + It 'ConciseView emits VT' { + $oldErrorView = $ErrorView + + try { + $ErrorView = 'ConciseView' + Invoke-Expression '1/d' + } + catch { + $e = $_ + } + finally { + $ErrorView = $oldErrorView + } + + $e | Out-String | Should -BeLikeExactly "*`e*" + } + + It 'Get-Error emits VT' { + try { + 1/0 + } + catch { + # ignore + } + + Get-Error | Out-String | Should -BeLikeExactly "*`e*" + } + } + + Context 'No Escape Sequences' { + BeforeAll { + $env:__SuppressAnsiEscapeSequences = 1 + } + + It 'Select-String does not emit VT' { + "select this string" | Select-String 'this' | Out-String | Should -Not -BeLikeExactly "*`e*" + } + + It 'ConciseView does not emit VT' { + $oldErrorView = $ErrorView + + try { + $ErrorView = 'ConciseView' + Invoke-Expression '1/d' + } + catch { + $e = $_ + } + finally { + $ErrorView = $oldErrorView + } + + $e | Out-String | Should -Not -BeLikeExactly "*`e*" + } + + It 'Get-Error does not emit VT' { + try { + 1/0 + } + catch { + # ignore + } + + Get-Error | Out-String | Should -Not -BeLikeExactly "*`e*" + } + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 index 8a73ddfe8b..9b5bc1b966 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 @@ -7,6 +7,10 @@ Describe "Select-String" -Tags "CI" { $currentDirectory = $pwd.Path } + AfterAll { + Push-Location $currentDirectory + } + Context "String actions" { $testinputone = "hello","Hello","goodbye" $testinputtwo = "hello","Hello" @@ -76,47 +80,47 @@ Describe "Select-String" -Tags "CI" { } it "Should output a string with the first match highlighted" { - if ($Host.UI.SupportsVirtualTerminal) + if ($Host.UI.SupportsVirtualTerminal -and !(Test-Path env:__SuppressAnsiEscapeSequences)) { $result = $testinputone | Select-String -Pattern "l" | Out-String - $result | Should -Be "`nhe`e[7ml`e[0mlo`nHe`e[7ml`e[0mlo`n`n" + $result | Should -Be "${nl}he`e[7ml`e[0mlo${nl}He`e[7ml`e[0mlo${nl}${nl}" } else { $result = $testinputone | Select-String -Pattern "l" | Out-String - $result | Should -Be "`nhello`nHello`n`n" + $result | Should -Be "${nl}hello${nl}Hello${nl}${nl}" } } it "Should output a string with all matches highlighted when AllMatch is used" { - if ($Host.UI.SupportsVirtualTerminal) + if ($Host.UI.SupportsVirtualTerminal -and !(Test-Path env:__SuppressAnsiEscapeSequences)) { $result = $testinputone | Select-String -Pattern "l" -AllMatch | Out-String - $result | Should -Be "`nhe`e[7ml`e[0m`e[7ml`e[0mo`nHe`e[7ml`e[0m`e[7ml`e[0mo`n`n" + $result | Should -Be "${nl}he`e[7ml`e[0m`e[7ml`e[0mo${nl}He`e[7ml`e[0m`e[7ml`e[0mo${nl}${nl}" } else { $result = $testinputone | Select-String -Pattern "l" -AllMatch | Out-String - $result | Should -Be "`nhello`nHello`n`n" + $result | Should -Be "${nl}hello${nl}Hello${nl}${nl}" } } it "Should output a string with the first match highlighted when SimpleMatch is used" { - if ($Host.UI.SupportsVirtualTerminal) + if ($Host.UI.SupportsVirtualTerminal -and !(Test-Path env:__SuppressAnsiEscapeSequences)) { $result = $testinputone | Select-String -Pattern "l" -SimpleMatch | Out-String - $result | Should -Be "`nhe`e[7ml`e[0mlo`nHe`e[7ml`e[0mlo`n`n" + $result | Should -Be "${nl}he`e[7ml`e[0mlo${nl}He`e[7ml`e[0mlo${nl}${nl}" } else { $result = $testinputone | Select-String -Pattern "l" -SimpleMatch | Out-String - $result | Should -Be "`nhello`nHello`n`n" + $result | Should -Be "${nl}hello${nl}Hello${nl}${nl}" } } it "Should output a string without highlighting when NoEmphasis is used" { $result = $testinputone | Select-String -Pattern "l" -NoEmphasis | Out-String - $result | Should -Be "`nhello`nHello`n`n" + $result | Should -Be "${nl}hello${nl}Hello${nl}${nl}" } it "Should return an array of matching strings without virtual terminal sequences" { @@ -247,8 +251,4 @@ Describe "Select-String" -Tags "CI" { Select-String second $testInputFile -Raw -Context 2,2 | Should -BeExactly $expected } } - - AfterAll { - Push-Location $currentDirectory - } }