From 5939770eda7b2b7c223c26b305f2aa60cfc6e43f Mon Sep 17 00:00:00 2001 From: Nate Hitze Date: Thu, 6 Jul 2023 15:13:02 -0400 Subject: [PATCH 1/3] Use E3 to clear console on Unix when possible Some terminals define an extra sequence to clear the terminal scroll buffer. Using it after the clear sequence makes Clear() work like the 'clear' command. Fix #84351 --- .../src/System/ConsolePal.Unix.cs | 4 ++ .../src/System/TerminalFormatStrings.cs | 3 ++ .../System.Console/tests/TermInfo.Unix.cs | 42 +++++++++++-------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs index 4159f83710256..97b44c02e3188 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs @@ -223,6 +223,10 @@ public static void Clear() if (!Console.IsOutputRedirected) { WriteStdoutAnsiString(TerminalFormatStringsInstance.Clear); + if (TerminalFormatStringsInstance.ClearScrollbackBuffer is not null) + { + WriteStdoutAnsiString(TerminalFormatStringsInstance.ClearScrollbackBuffer); + } } } diff --git a/src/libraries/System.Console/src/System/TerminalFormatStrings.cs b/src/libraries/System.Console/src/System/TerminalFormatStrings.cs index 398422d41cca0..9ce52915e7499 100644 --- a/src/libraries/System.Console/src/System/TerminalFormatStrings.cs +++ b/src/libraries/System.Console/src/System/TerminalFormatStrings.cs @@ -31,6 +31,8 @@ internal sealed class TerminalFormatStrings public readonly string? Bell; /// The format string to use to clear the terminal. public readonly string? Clear; + /// The format string to use to clear the terminal scrollback buffer, if supported. + public readonly string? ClearScrollbackBuffer; /// The format string to use to set the position of the cursor. public readonly string? CursorAddress; /// The format string to use to move the cursor to the left. @@ -73,6 +75,7 @@ public TerminalFormatStrings(TermInfo.Database? db) Reset = db.GetString(TermInfo.WellKnownStrings.OrigPairs) ?? db.GetString(TermInfo.WellKnownStrings.OrigColors); Bell = db.GetString(TermInfo.WellKnownStrings.Bell); Clear = db.GetString(TermInfo.WellKnownStrings.Clear); + ClearScrollbackBuffer = db.GetExtendedString("E3"); Columns = db.GetNumber(TermInfo.WellKnownNumbers.Columns); Lines = db.GetNumber(TermInfo.WellKnownNumbers.Lines); CursorVisible = db.GetString(TermInfo.WellKnownStrings.CursorVisible); diff --git a/src/libraries/System.Console/tests/TermInfo.Unix.cs b/src/libraries/System.Console/tests/TermInfo.Unix.cs index 87b50d8d15a9e..21e91cf3ea50d 100644 --- a/src/libraries/System.Console/tests/TermInfo.Unix.cs +++ b/src/libraries/System.Console/tests/TermInfo.Unix.cs @@ -75,22 +75,22 @@ public void VerifyTermInfoSupportsNewAndLegacyNcurses() [Theory] [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo - [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] - [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1)] - [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8)] - [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] - [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2)] - [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9)] - [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] - [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3)] - [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10)] - [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] - [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4)] - [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11)] - [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] - [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5)] - [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12)] - public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue) + [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, true)] + [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1, true)] + [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8, true)] + [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] + [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2, false)] + [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9, false)] + [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] + [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3, false)] + [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10, false)] + [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] + [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4, false)] + [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11, false)] + [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] + [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5, false)] + [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12, false)] + public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue, bool expectedScrollbackClearSupport) { TermInfo.Database db = TermInfo.DatabaseFactory.ReadDatabase(termToTest); if (db != null) @@ -99,6 +99,14 @@ public void TermInfoVerification(string termToTest, string expectedForeground, s Assert.Equal(expectedForeground, TermInfo.ParameterizedStrings.Evaluate(info.Foreground, colorValue)); Assert.Equal(expectedBackground, TermInfo.ParameterizedStrings.Evaluate(info.Background, colorValue)); Assert.InRange(info.MaxColors, 1, int.MaxValue); + if (expectedScrollbackClearSupport) + { + Assert.NotNull(info.ClearScrollbackBuffer); + } + else + { + Assert.Null(info.ClearScrollbackBuffer); + } } } @@ -108,7 +116,7 @@ public void EmuTermInfoDoesntBreakParser() { // This file (available by default on OS X) is called out specifically since it contains a format where it has %i // but only one variable instead of two. Make sure we don't break in this case - TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0); + TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0, false); } [Fact] From 832b6c4ac94db93652b254784d8bb649a260f48b Mon Sep 17 00:00:00 2001 From: Nate Hitze Date: Thu, 6 Jul 2023 17:17:31 -0400 Subject: [PATCH 2/3] Use IsNullOrEmpty instead of null check --- .../System.Console/src/System/ConsolePal.Unix.cs | 2 +- src/libraries/System.Console/tests/TermInfo.Unix.cs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs index 97b44c02e3188..6e1d74159b0d2 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs @@ -223,7 +223,7 @@ public static void Clear() if (!Console.IsOutputRedirected) { WriteStdoutAnsiString(TerminalFormatStringsInstance.Clear); - if (TerminalFormatStringsInstance.ClearScrollbackBuffer is not null) + if (!string.IsNullOrEmpty(TerminalFormatStringsInstance.ClearScrollbackBuffer)) { WriteStdoutAnsiString(TerminalFormatStringsInstance.ClearScrollbackBuffer); } diff --git a/src/libraries/System.Console/tests/TermInfo.Unix.cs b/src/libraries/System.Console/tests/TermInfo.Unix.cs index 21e91cf3ea50d..bd03161f148e3 100644 --- a/src/libraries/System.Console/tests/TermInfo.Unix.cs +++ b/src/libraries/System.Console/tests/TermInfo.Unix.cs @@ -99,14 +99,7 @@ public void TermInfoVerification(string termToTest, string expectedForeground, s Assert.Equal(expectedForeground, TermInfo.ParameterizedStrings.Evaluate(info.Foreground, colorValue)); Assert.Equal(expectedBackground, TermInfo.ParameterizedStrings.Evaluate(info.Background, colorValue)); Assert.InRange(info.MaxColors, 1, int.MaxValue); - if (expectedScrollbackClearSupport) - { - Assert.NotNull(info.ClearScrollbackBuffer); - } - else - { - Assert.Null(info.ClearScrollbackBuffer); - } + Assert.Equal(expectedScrollbackClearSupport, !string.IsNullOrEmpty(info.ClearScrollbackBuffer)); } } From 4bc8eb68f38d2f38eab8055b9f475c7cc9326faf Mon Sep 17 00:00:00 2001 From: Nate Hitze Date: Fri, 7 Jul 2023 10:26:00 -0400 Subject: [PATCH 3/3] Send E3 before clear and fix tests --- .../src/System/ConsolePal.Unix.cs | 2 +- .../System.Console/tests/TermInfo.Unix.cs | 50 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs index 6e1d74159b0d2..3a3c6c14cf248 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs @@ -222,11 +222,11 @@ public static void Clear() { if (!Console.IsOutputRedirected) { - WriteStdoutAnsiString(TerminalFormatStringsInstance.Clear); if (!string.IsNullOrEmpty(TerminalFormatStringsInstance.ClearScrollbackBuffer)) { WriteStdoutAnsiString(TerminalFormatStringsInstance.ClearScrollbackBuffer); } + WriteStdoutAnsiString(TerminalFormatStringsInstance.Clear); } } diff --git a/src/libraries/System.Console/tests/TermInfo.Unix.cs b/src/libraries/System.Console/tests/TermInfo.Unix.cs index bd03161f148e3..a67ffd3fe8972 100644 --- a/src/libraries/System.Console/tests/TermInfo.Unix.cs +++ b/src/libraries/System.Console/tests/TermInfo.Unix.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Tests; using Xunit; [SkipOnPlatform(TestPlatforms.Android | TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Android, Browser, iOS, MacCatalyst, or tvOS.")] @@ -75,22 +76,22 @@ public void VerifyTermInfoSupportsNewAndLegacyNcurses() [Theory] [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo - [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, true)] - [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1, true)] - [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8, true)] - [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] - [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2, false)] - [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9, false)] - [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] - [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3, false)] - [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10, false)] - [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] - [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4, false)] - [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11, false)] - [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0, false)] - [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5, false)] - [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12, false)] - public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue, bool expectedScrollbackClearSupport) + [InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] + [InlineData("xterm-256color", "\u001B\u005B\u00331m", "\u001B\u005B\u00341m", 1)] + [InlineData("xterm-256color", "\u001B\u005B90m", "\u001B\u005B100m", 8)] + [InlineData("screen", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] + [InlineData("screen", "\u001B\u005B\u00332m", "\u001B\u005B\u00342m", 2)] + [InlineData("screen", "\u001B\u005B\u00339m", "\u001B\u005B\u00349m", 9)] + [InlineData("Eterm", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] + [InlineData("Eterm", "\u001B\u005B\u00333m", "\u001B\u005B\u00343m", 3)] + [InlineData("Eterm", "\u001B\u005B\u003310m", "\u001B\u005B\u003410m", 10)] + [InlineData("wsvt25", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] + [InlineData("wsvt25", "\u001B\u005B\u00334m", "\u001B\u005B\u00344m", 4)] + [InlineData("wsvt25", "\u001B\u005B\u003311m", "\u001B\u005B\u003411m", 11)] + [InlineData("mach-color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)] + [InlineData("mach-color", "\u001B\u005B\u00335m", "\u001B\u005B\u00345m", 5)] + [InlineData("mach-color", "\u001B\u005B\u003312m", "\u001B\u005B\u003412m", 12)] + public void TermInfoVerification(string termToTest, string expectedForeground, string expectedBackground, int colorValue) { TermInfo.Database db = TermInfo.DatabaseFactory.ReadDatabase(termToTest); if (db != null) @@ -99,17 +100,30 @@ public void TermInfoVerification(string termToTest, string expectedForeground, s Assert.Equal(expectedForeground, TermInfo.ParameterizedStrings.Evaluate(info.Foreground, colorValue)); Assert.Equal(expectedBackground, TermInfo.ParameterizedStrings.Evaluate(info.Background, colorValue)); Assert.InRange(info.MaxColors, 1, int.MaxValue); - Assert.Equal(expectedScrollbackClearSupport, !string.IsNullOrEmpty(info.ClearScrollbackBuffer)); } } + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo + public void TermInfoE3CanBeRead() + { + bool ScrollbackBufferIsSet(TerminalData data) => !string.IsNullOrEmpty(data.TerminalDb.ClearScrollbackBuffer); + + // XTerm defines E3 for clearing scrollback buffer and tmux does not. + // This can't be added to TermInfoVerification because xterm-256color sometimes has E3 defined (e.g. on Ubuntu but not macOS) + XTermData xTermData = new(); + Assert.True(ScrollbackBufferIsSet(xTermData)); + TmuxData tmuxData = new(); + Assert.False(ScrollbackBufferIsSet(tmuxData)); + } + [Fact] [PlatformSpecific(TestPlatforms.OSX)] // The file being tested is available by default only on OSX public void EmuTermInfoDoesntBreakParser() { // This file (available by default on OS X) is called out specifically since it contains a format where it has %i // but only one variable instead of two. Make sure we don't break in this case - TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0, false); + TermInfoVerification("emu", "\u001Br1;", "\u001Bs1;", 0); } [Fact]