mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Phil Scott
						Phil Scott
					
				
			
			
				
	
			
			
			 Phil Scott
						Phil Scott
					
				
			
						parent
						
							2e5d18fa78
						
					
				
				
					commit
					fd4b96944e
				
			
							
								
								
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
							
								
								
									
										15
									
								
								examples/Console/AlternateScreen/AlternateScreen.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples/Console/AlternateScreen/AlternateScreen.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|     <ExampleTitle>Screens</ExampleTitle> | ||||
|     <ExampleDescription>Demonstrates how to use alternate screens.</ExampleDescription> | ||||
|     <ExampleGroup>Widgets</ExampleGroup> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\..\Shared\Shared.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										26
									
								
								examples/Console/AlternateScreen/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								examples/Console/AlternateScreen/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
| }); | ||||
| @@ -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 | ||||
|   | ||||
| @@ -10,7 +10,7 @@ namespace Spectre.Console.Testing | ||||
|             return func(); | ||||
|         } | ||||
|  | ||||
|         public async Task<T> Run<T>(Func<Task<T>> func) | ||||
|         public async Task<T> RunAsync<T>(Func<Task<T>> func) | ||||
|         { | ||||
|             return await func().ConfigureAwait(false); | ||||
|         } | ||||
|   | ||||
| @@ -66,6 +66,12 @@ namespace Spectre.Console.Testing | ||||
|             _input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public bool IsKeyAvailable() | ||||
|         { | ||||
|             return _input.Count > 0; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public ConsoleKeyInfo? ReadKey(bool intercept) | ||||
|         { | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/Spectre.Console/AnsiConsole.Screen.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Spectre.Console/AnsiConsole.Screen.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A console capable of writing ANSI escape sequences. | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsole | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Switches to an alternate screen buffer if the terminal supports it. | ||||
|         /// </summary> | ||||
|         /// <param name="action">The action to execute within the alternate screen buffer.</param> | ||||
|         public static void AlternateScreen(Action action) | ||||
|         { | ||||
|             Console.AlternateScreen(action); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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( | ||||
|   | ||||
| @@ -55,6 +55,12 @@ namespace Spectre.Console | ||||
|         /// </summary> | ||||
|         public bool Unicode { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets a value indicating whether | ||||
|         /// or not the console supports alternate buffers. | ||||
|         /// </summary> | ||||
|         public bool AlternateBuffer { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the | ||||
|         /// <see cref="Capabilities"/> class. | ||||
|   | ||||
| @@ -29,7 +29,7 @@ namespace Spectre.Console | ||||
|         /// <returns>The result of the function.</returns> | ||||
|         public static Task<T> RunExclusive<T>(this IAnsiConsole console, Func<Task<T>> func) | ||||
|         { | ||||
|             return console.ExclusivityMode.Run(func); | ||||
|             return console.ExclusivityMode.RunAsync(func); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,48 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="IAnsiConsole"/>. | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsoleExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Switches to an alternate screen buffer if the terminal supports it. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console.</param> | ||||
|         /// <param name="action">The action to execute within the alternate screen buffer.</param> | ||||
|         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<object?>(() => | ||||
|             { | ||||
|                 // Switch to alternate screen | ||||
|                 console.Write(new ControlCode("\u001b[?1049h\u001b[H")); | ||||
|  | ||||
|                 // Execute custom action | ||||
|                 action(); | ||||
|  | ||||
|                 // Switch back to primary screen | ||||
|                 console.Write(new ControlCode("\u001b[?1049l")); | ||||
|  | ||||
|                 // Dummy result | ||||
|                 return null; | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -9,6 +9,13 @@ namespace Spectre.Console | ||||
|     /// </summary> | ||||
|     public interface IAnsiConsoleInput | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets a value indicating whether or not | ||||
|         /// there is a key available. | ||||
|         /// </summary> | ||||
|         /// <returns><c>true</c> if there's a key available, otherwise <c>false</c>.</returns> | ||||
|         bool IsKeyAvailable(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Reads a key from the console. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -22,6 +22,6 @@ namespace Spectre.Console | ||||
|         /// <typeparam name="T">The result type.</typeparam> | ||||
|         /// <param name="func">The func to run in exclusive mode.</param> | ||||
|         /// <returns>The result of the function.</returns> | ||||
|         Task<T> Run<T>(Func<Task<T>> func); | ||||
|         Task<T> RunAsync<T>(Func<Task<T>> func); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ namespace Spectre.Console.Internal | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public async Task<T> Run<T>(Func<Task<T>> func) | ||||
|         public async Task<T> RunAsync<T>(Func<Task<T>> func) | ||||
|         { | ||||
|             // Try acquiring the exclusivity semaphore | ||||
|             if (!await _semaphore.WaitAsync(0).ConfigureAwait(false)) | ||||
|   | ||||
| @@ -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<ConsoleKeyInfo?> 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) | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| Foo | ||||
| [?1049h[HBar | ||||
| [?1049l | ||||
							
								
								
									
										79
									
								
								test/Spectre.Console.Tests/Unit/AlternateScreenTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								test/Spectre.Console.Tests/Unit/AlternateScreenTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user