mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +08:00
parent
2e5d18fa78
commit
fd4b96944e
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -93,7 +93,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
dotnet tool restore
|
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
|
- name: Build
|
||||||
shell: bash
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTable\LiveTable.csproj", "{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTable\LiveTable.csproj", "{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -10,7 +10,7 @@ namespace Spectre.Console.Testing
|
|||||||
return func();
|
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);
|
return await func().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,12 @@ namespace Spectre.Console.Testing
|
|||||||
_input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false));
|
_input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsKeyAvailable()
|
||||||
|
{
|
||||||
|
return _input.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ConsoleKeyInfo? ReadKey(bool intercept)
|
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.Legacy = legacyConsole;
|
||||||
profile.Capabilities.Interactive = interactive;
|
profile.Capabilities.Interactive = interactive;
|
||||||
profile.Capabilities.Unicode = encoding.EncodingName.ContainsExact("Unicode");
|
profile.Capabilities.Unicode = encoding.EncodingName.ContainsExact("Unicode");
|
||||||
|
profile.Capabilities.AlternateBuffer = supportsAnsi && !legacyConsole;
|
||||||
|
|
||||||
// Enrich the profile
|
// Enrich the profile
|
||||||
ProfileEnricher.Enrich(
|
ProfileEnricher.Enrich(
|
||||||
|
@ -55,6 +55,12 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Unicode { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the
|
/// Initializes a new instance of the
|
||||||
/// <see cref="Capabilities"/> class.
|
/// <see cref="Capabilities"/> class.
|
||||||
|
@ -29,7 +29,7 @@ namespace Spectre.Console
|
|||||||
/// <returns>The result of the function.</returns>
|
/// <returns>The result of the function.</returns>
|
||||||
public static Task<T> RunExclusive<T>(this IAnsiConsole console, Func<Task<T>> func)
|
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>
|
/// </summary>
|
||||||
public interface IAnsiConsoleInput
|
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>
|
/// <summary>
|
||||||
/// Reads a key from the console.
|
/// Reads a key from the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +22,6 @@ namespace Spectre.Console
|
|||||||
/// <typeparam name="T">The result type.</typeparam>
|
/// <typeparam name="T">The result type.</typeparam>
|
||||||
/// <param name="func">The func to run in exclusive mode.</param>
|
/// <param name="func">The func to run in exclusive mode.</param>
|
||||||
/// <returns>The result of the function.</returns>
|
/// <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
|
// Try acquiring the exclusivity semaphore
|
||||||
if (!await _semaphore.WaitAsync(0).ConfigureAwait(false))
|
if (!await _semaphore.WaitAsync(0).ConfigureAwait(false))
|
||||||
|
@ -13,6 +13,16 @@ namespace Spectre.Console
|
|||||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
_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)
|
public ConsoleKeyInfo? ReadKey(bool intercept)
|
||||||
{
|
{
|
||||||
if (!_profile.Capabilities.Interactive)
|
if (!_profile.Capabilities.Interactive)
|
||||||
@ -20,16 +30,16 @@ namespace Spectre.Console
|
|||||||
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
|
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!System.Console.KeyAvailable)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return System.Console.ReadKey(intercept);
|
return System.Console.ReadKey(intercept);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
|
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)
|
while (true)
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user