diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7481fec..e236adf 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -93,7 +93,7 @@ jobs:
shell: bash
run: |
dotnet tool restore
- dotnet example --all --skip live --skip livetable --skip prompt
+ dotnet example --all --skip live --skip livetable --skip prompt --skip screens
- name: Build
shell: bash
diff --git a/examples/Console/AlternateScreen/AlternateScreen.csproj b/examples/Console/AlternateScreen/AlternateScreen.csproj
new file mode 100644
index 0000000..24be5e7
--- /dev/null
+++ b/examples/Console/AlternateScreen/AlternateScreen.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net6.0
+ Screens
+ Demonstrates how to use alternate screens.
+ Widgets
+
+
+
+
+
+
+
diff --git a/examples/Console/AlternateScreen/Program.cs b/examples/Console/AlternateScreen/Program.cs
new file mode 100644
index 0000000..4415ab0
--- /dev/null
+++ b/examples/Console/AlternateScreen/Program.cs
@@ -0,0 +1,26 @@
+// Check if we can use alternate screen buffers
+using Spectre.Console;
+
+if (!AnsiConsole.Profile.Capabilities.AlternateBuffer)
+{
+ AnsiConsole.MarkupLine(
+ "[red]Alternate screen buffers are not supported " +
+ "by your terminal[/] [yellow]:([/]");
+
+ return;
+}
+
+// Write to the terminal
+AnsiConsole.Write(new Rule("[yellow]Normal universe[/]"));
+AnsiConsole.Write(new Panel("Hello World!"));
+AnsiConsole.MarkupLine("[grey]Press a key to continue[/]");
+AnsiConsole.Console.Input.ReadKey(true);
+
+AnsiConsole.AlternateScreen(() =>
+{
+ // Now we're in another terminal screen buffer
+ AnsiConsole.Write(new Rule("[red]Mirror universe[/]"));
+ AnsiConsole.Write(new Panel("[red]Welcome to the upside down![/]"));
+ AnsiConsole.MarkupLine("[grey]Press a key to return[/]");
+ AnsiConsole.Console.Input.ReadKey(true);
+});
\ No newline at end of file
diff --git a/examples/Examples.sln b/examples/Examples.sln
index 079c546..dc89d2f 100644
--- a/examples/Examples.sln
+++ b/examples/Examples.sln
@@ -65,7 +65,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "Console\Trees\Tree
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTable\LiveTable.csproj", "{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minimal", "Console\Minimal\Minimal.csproj", "{1780A30A-397A-4CC3-B2A0-A385D9081FA2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Minimal", "Console\Minimal\Minimal.csproj", "{1780A30A-397A-4CC3-B2A0-A385D9081FA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlternateScreen", "Console\AlternateScreen\AlternateScreen.csproj", "{8A3B636E-5828-438B-A8F4-83811D2704CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -437,6 +439,18 @@ Global
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x64.Build.0 = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x86.ActiveCfg = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x86.Build.0 = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|x64.Build.0 = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Debug|x86.Build.0 = Debug|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|x64.ActiveCfg = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|x64.Build.0 = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|x86.ActiveCfg = Release|Any CPU
+ {8A3B636E-5828-438B-A8F4-83811D2704CD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Spectre.Console.Testing/Internal/NoopExclusivityMode.cs b/src/Spectre.Console.Testing/Internal/NoopExclusivityMode.cs
index bcc9be6..dd5aab5 100644
--- a/src/Spectre.Console.Testing/Internal/NoopExclusivityMode.cs
+++ b/src/Spectre.Console.Testing/Internal/NoopExclusivityMode.cs
@@ -10,7 +10,7 @@ namespace Spectre.Console.Testing
return func();
}
- public async Task Run(Func> func)
+ public async Task RunAsync(Func> func)
{
return await func().ConfigureAwait(false);
}
diff --git a/src/Spectre.Console.Testing/TestConsoleInput.cs b/src/Spectre.Console.Testing/TestConsoleInput.cs
index 837dff9..1342e29 100644
--- a/src/Spectre.Console.Testing/TestConsoleInput.cs
+++ b/src/Spectre.Console.Testing/TestConsoleInput.cs
@@ -66,6 +66,12 @@ namespace Spectre.Console.Testing
_input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false));
}
+ ///
+ public bool IsKeyAvailable()
+ {
+ return _input.Count > 0;
+ }
+
///
public ConsoleKeyInfo? ReadKey(bool intercept)
{
diff --git a/src/Spectre.Console/AnsiConsole.Screen.cs b/src/Spectre.Console/AnsiConsole.Screen.cs
new file mode 100644
index 0000000..69e46ba
--- /dev/null
+++ b/src/Spectre.Console/AnsiConsole.Screen.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Spectre.Console
+{
+ ///
+ /// A console capable of writing ANSI escape sequences.
+ ///
+ public static partial class AnsiConsole
+ {
+ ///
+ /// Switches to an alternate screen buffer if the terminal supports it.
+ ///
+ /// The action to execute within the alternate screen buffer.
+ public static void AlternateScreen(Action action)
+ {
+ Console.AlternateScreen(action);
+ }
+ }
+}
diff --git a/src/Spectre.Console/AnsiConsoleFactory.cs b/src/Spectre.Console/AnsiConsoleFactory.cs
index 3d8e0e5..178ea9d 100644
--- a/src/Spectre.Console/AnsiConsoleFactory.cs
+++ b/src/Spectre.Console/AnsiConsoleFactory.cs
@@ -55,6 +55,7 @@ namespace Spectre.Console
profile.Capabilities.Legacy = legacyConsole;
profile.Capabilities.Interactive = interactive;
profile.Capabilities.Unicode = encoding.EncodingName.ContainsExact("Unicode");
+ profile.Capabilities.AlternateBuffer = supportsAnsi && !legacyConsole;
// Enrich the profile
ProfileEnricher.Enrich(
diff --git a/src/Spectre.Console/Capabilities.cs b/src/Spectre.Console/Capabilities.cs
index 3419640..3e2a168 100644
--- a/src/Spectre.Console/Capabilities.cs
+++ b/src/Spectre.Console/Capabilities.cs
@@ -55,6 +55,12 @@ namespace Spectre.Console
///
public bool Unicode { get; set; }
+ ///
+ /// Gets or sets a value indicating whether
+ /// or not the console supports alternate buffers.
+ ///
+ public bool AlternateBuffer { get; set; }
+
///
/// Initializes a new instance of the
/// class.
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs
index 10ba8e6..0bdc535 100644
--- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs
@@ -29,7 +29,7 @@ namespace Spectre.Console
/// The result of the function.
public static Task RunExclusive(this IAnsiConsole console, Func> func)
{
- return console.ExclusivityMode.Run(func);
+ return console.ExclusivityMode.RunAsync(func);
}
}
}
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs
new file mode 100644
index 0000000..1491bfb
--- /dev/null
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Screen.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Spectre.Console
+{
+ ///
+ /// Contains extension methods for .
+ ///
+ public static partial class AnsiConsoleExtensions
+ {
+ ///
+ /// Switches to an alternate screen buffer if the terminal supports it.
+ ///
+ /// The console.
+ /// The action to execute within the alternate screen buffer.
+ public static void AlternateScreen(this IAnsiConsole console, Action action)
+ {
+ if (console is null)
+ {
+ throw new ArgumentNullException(nameof(console));
+ }
+
+ if (!console.Profile.Capabilities.Ansi)
+ {
+ throw new NotSupportedException("Alternate buffers are not supported since your terminal does not support ANSI.");
+ }
+
+ if (!console.Profile.Capabilities.AlternateBuffer)
+ {
+ throw new NotSupportedException("Alternate buffers are not supported by your terminal.");
+ }
+
+ console.ExclusivityMode.Run
public interface IAnsiConsoleInput
{
+ ///
+ /// Gets a value indicating whether or not
+ /// there is a key available.
+ ///
+ /// true if there's a key available, otherwise false.
+ bool IsKeyAvailable();
+
///
/// Reads a key from the console.
///
diff --git a/src/Spectre.Console/IExclusivityMode.cs b/src/Spectre.Console/IExclusivityMode.cs
index fae0c41..8fa1b29 100644
--- a/src/Spectre.Console/IExclusivityMode.cs
+++ b/src/Spectre.Console/IExclusivityMode.cs
@@ -22,6 +22,6 @@ namespace Spectre.Console
/// The result type.
/// The func to run in exclusive mode.
/// The result of the function.
- Task Run(Func> func);
+ Task RunAsync(Func> func);
}
}
diff --git a/src/Spectre.Console/Internal/DefaultExclusivityMode.cs b/src/Spectre.Console/Internal/DefaultExclusivityMode.cs
index 1c675e9..02bd830 100644
--- a/src/Spectre.Console/Internal/DefaultExclusivityMode.cs
+++ b/src/Spectre.Console/Internal/DefaultExclusivityMode.cs
@@ -31,7 +31,7 @@ namespace Spectre.Console.Internal
}
}
- public async Task Run(Func> func)
+ public async Task RunAsync(Func> func)
{
// Try acquiring the exclusivity semaphore
if (!await _semaphore.WaitAsync(0).ConfigureAwait(false))
diff --git a/src/Spectre.Console/Internal/DefaultInput.cs b/src/Spectre.Console/Internal/DefaultInput.cs
index 846403c..2b65a25 100644
--- a/src/Spectre.Console/Internal/DefaultInput.cs
+++ b/src/Spectre.Console/Internal/DefaultInput.cs
@@ -13,6 +13,16 @@ namespace Spectre.Console
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
}
+ public bool IsKeyAvailable()
+ {
+ if (!_profile.Capabilities.Interactive)
+ {
+ throw new InvalidOperationException("Failed to read input in non-interactive mode.");
+ }
+
+ return System.Console.KeyAvailable;
+ }
+
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (!_profile.Capabilities.Interactive)
@@ -20,16 +30,16 @@ namespace Spectre.Console
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
}
- if (!System.Console.KeyAvailable)
- {
- return null;
- }
-
return System.Console.ReadKey(intercept);
}
public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
+ if (!_profile.Capabilities.Interactive)
+ {
+ throw new InvalidOperationException("Failed to read input in non-interactive mode.");
+ }
+
while (true)
{
if (cancellationToken.IsCancellationRequested)
diff --git a/test/Spectre.Console.Tests/Expectations/AlternateScreen/Show.Output.verified.txt b/test/Spectre.Console.Tests/Expectations/AlternateScreen/Show.Output.verified.txt
new file mode 100644
index 0000000..205b2d0
--- /dev/null
+++ b/test/Spectre.Console.Tests/Expectations/AlternateScreen/Show.Output.verified.txt
@@ -0,0 +1,3 @@
+Foo
+[?1049h[HBar
+[?1049l
\ No newline at end of file
diff --git a/test/Spectre.Console.Tests/Unit/AlternateScreenTests.cs b/test/Spectre.Console.Tests/Unit/AlternateScreenTests.cs
new file mode 100644
index 0000000..038f9ae
--- /dev/null
+++ b/test/Spectre.Console.Tests/Unit/AlternateScreenTests.cs
@@ -0,0 +1,79 @@
+using System.Threading.Tasks;
+using Shouldly;
+using Spectre.Console.Testing;
+using Spectre.Verify.Extensions;
+using VerifyXunit;
+using Xunit;
+
+namespace Spectre.Console.Tests.Unit
+{
+ [UsesVerify]
+ [ExpectationPath("AlternateScreen")]
+ public sealed class AlternateScreenTests
+ {
+ [Fact]
+ public void Should_Throw_If_Alternative_Buffer_Is_Not_Supported_By_Terminal()
+ {
+ // Given
+ var console = new TestConsole();
+ console.Profile.Capabilities.AlternateBuffer = false;
+
+ // When
+ var result = Record.Exception(() =>
+ {
+ console.WriteLine("Foo");
+ console.AlternateScreen(() =>
+ {
+ console.WriteLine("Bar");
+ });
+ });
+
+ // Then
+ result.ShouldNotBeNull();
+ result.Message.ShouldBe("Alternate buffers are not supported by your terminal.");
+ }
+
+ [Fact]
+ public void Should_Throw_If_Ansi_Is_Not_Supported_By_Terminal()
+ {
+ // Given
+ var console = new TestConsole();
+ console.Profile.Capabilities.Ansi = false;
+ console.Profile.Capabilities.AlternateBuffer = true;
+
+ // When
+ var result = Record.Exception(() =>
+ {
+ console.WriteLine("Foo");
+ console.AlternateScreen(() =>
+ {
+ console.WriteLine("Bar");
+ });
+ });
+
+ // Then
+ result.ShouldNotBeNull();
+ result.Message.ShouldBe("Alternate buffers are not supported since your terminal does not support ANSI.");
+ }
+
+ [Fact]
+ [Expectation("Show")]
+ public async Task Should_Write_To_Alternate_Screen()
+ {
+ // Given
+ var console = new TestConsole();
+ console.EmitAnsiSequences = true;
+ console.Profile.Capabilities.AlternateBuffer = true;
+
+ // When
+ console.WriteLine("Foo");
+ console.AlternateScreen(() =>
+ {
+ console.WriteLine("Bar");
+ });
+
+ // Then
+ await Verifier.Verify(console.Output);
+ }
+ }
+}