Support cancellation of prompts

Closes #417
This commit is contained in:
Patrik Svensson 2021-07-10 21:03:13 +02:00 committed by Phil Scott
parent 884cb8ddd4
commit 5f97f2300c
15 changed files with 117 additions and 26 deletions

View File

@ -25,7 +25,6 @@ namespace Spectre.Console.Examples
var age = AskAge(); var age = AskAge();
var password = AskPassword(); var password = AskPassword();
var color = AskColor(); var color = AskColor();
var origin = AskOrigin();
// Summary // Summary
AnsiConsole.WriteLine(); AnsiConsole.WriteLine();
@ -38,8 +37,7 @@ namespace Spectre.Console.Examples
.AddRow("[grey]Favorite sport[/]", sport) .AddRow("[grey]Favorite sport[/]", sport)
.AddRow("[grey]Age[/]", age.ToString()) .AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password) .AddRow("[grey]Password[/]", password)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color) .AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
.AddRow("[grey]Origin[/]", origin));
} }
private static string AskName() private static string AskName()
@ -143,14 +141,5 @@ namespace Spectre.Console.Examples
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?") new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
.AllowEmpty()); .AllowEmpty());
} }
private static string AskOrigin()
{
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Default answer[/]").RuleStyle("grey").LeftAligned());
var name = AnsiConsole.Ask("Where are you [green]from[/]?", "Earth");
return name;
}
} }
} }

View File

@ -92,4 +92,10 @@ dotnet_diagnostic.IDE0004.severity = warning
dotnet_diagnostic.CA1810.severity = none dotnet_diagnostic.CA1810.severity = none
# IDE0044: Add readonly modifier # IDE0044: Add readonly modifier
dotnet_diagnostic.IDE0044.severity = warning dotnet_diagnostic.IDE0044.severity = warning
# RCS1047: Non-asynchronous method name should not end with 'Async'.
dotnet_diagnostic.RCS1047.severity = none
# RCS1090: Call 'ConfigureAwait(false)'.
dotnet_diagnostic.RCS1090.severity = warning

View File

@ -12,7 +12,7 @@ namespace Spectre.Console.Testing
public async Task<T> Run<T>(Func<Task<T>> func) public async Task<T> Run<T>(Func<Task<T>> func)
{ {
return await func(); return await func().ConfigureAwait(false);
} }
} }
} }

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console.Testing namespace Spectre.Console.Testing
{ {
@ -74,5 +76,11 @@ namespace Spectre.Console.Testing
return _input.Dequeue(); return _input.Dequeue();
} }
/// <inheritdoc/>
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
}
} }
} }

View File

@ -81,7 +81,7 @@ namespace Spectre.Console.Cli
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data); var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
// Execute the command tree. // Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration); return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
} }
} }

View File

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -10,7 +12,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
public static partial class AnsiConsoleExtensions public static partial class AnsiConsoleExtensions
{ {
internal static string ReadLine(this IAnsiConsole console, Style? style, bool secret, IEnumerable<string>? items = null) internal static async Task<string> ReadLine(this IAnsiConsole console, Style? style, bool secret, IEnumerable<string>? items = null, CancellationToken cancellationToken = default)
{ {
if (console is null) if (console is null)
{ {
@ -24,7 +26,7 @@ namespace Spectre.Console
while (true) while (true)
{ {
var rawKey = console.Input.ReadKey(true); var rawKey = await console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
if (rawKey == null) if (rawKey == null)
{ {
continue; continue;

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -13,5 +15,13 @@ namespace Spectre.Console
/// <param name="intercept">Whether or not to intercept the key.</param> /// <param name="intercept">Whether or not to intercept the key.</param>
/// <returns>The key that was read.</returns> /// <returns>The key that was read.</returns>
ConsoleKeyInfo? ReadKey(bool intercept); ConsoleKeyInfo? ReadKey(bool intercept);
/// <summary>
/// Reads a key from the console.
/// </summary>
/// <param name="intercept">Whether or not to intercept the key.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The key that was read.</returns>
Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken);
} }
} }

View File

@ -46,7 +46,7 @@ namespace Spectre.Console.Internal
try try
{ {
return await func(); return await func().ConfigureAwait(false);
} }
finally finally
{ {

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -18,7 +20,32 @@ 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)
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
return null;
}
if (System.Console.KeyAvailable)
{
break;
}
await Task.Delay(5, cancellationToken).ConfigureAwait(false);
}
return ReadKey(intercept);
}
} }
} }

View File

@ -1,3 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
@ -50,6 +53,12 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public bool Show(IAnsiConsole console) public bool Show(IAnsiConsole console)
{
return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async Task<bool> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
{ {
var prompt = new TextPrompt<char>(_prompt) var prompt = new TextPrompt<char>(_prompt)
.InvalidChoiceMessage(InvalidChoiceMessage) .InvalidChoiceMessage(InvalidChoiceMessage)
@ -60,7 +69,7 @@ namespace Spectre.Console
.AddChoice(Yes) .AddChoice(Yes)
.AddChoice(No); .AddChoice(No);
var result = prompt.Show(console); var result = await prompt.ShowAsync(console, cancellationToken).ConfigureAwait(false);
return result == Yes; return result == Yes;
} }
} }

View File

@ -1,3 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
@ -12,5 +15,13 @@ namespace Spectre.Console
/// <param name="console">The console.</param> /// <param name="console">The console.</param>
/// <returns>The prompt input result.</returns> /// <returns>The prompt input result.</returns>
T Show(IAnsiConsole console); T Show(IAnsiConsole console);
/// <summary>
/// Shows the prompt asynchronously.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>The prompt input result.</returns>
Task<T> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken);
} }
} }

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@ -16,7 +18,10 @@ namespace Spectre.Console
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); _strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
} }
public ListPromptState<T> Show(ListPromptTree<T> tree, int requestedPageSize = 15) public async Task<ListPromptState<T>> Show(
ListPromptTree<T> tree,
CancellationToken cancellationToken,
int requestedPageSize = 15)
{ {
if (tree is null) if (tree is null)
{ {
@ -48,7 +53,7 @@ namespace Spectre.Console
while (true) while (true)
{ {
var rawKey = _console.Input.ReadKey(true); var rawKey = await _console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
if (rawKey == null) if (rawKey == null)
{ {
continue; continue;

View File

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@ -85,10 +87,16 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public List<T> Show(IAnsiConsole console) public List<T> Show(IAnsiConsole console)
{
return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async Task<List<T>> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
{ {
// Create the list prompt // Create the list prompt
var prompt = new ListPrompt<T>(console, this); var prompt = new ListPrompt<T>(console, this);
var result = prompt.Show(Tree, PageSize); var result = await prompt.Show(Tree, cancellationToken, PageSize).ConfigureAwait(false);
if (Mode == SelectionMode.Leaf) if (Mode == SelectionMode.Leaf)
{ {

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@ -74,10 +76,16 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public T Show(IAnsiConsole console) public T Show(IAnsiConsole console)
{
return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async Task<T> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
{ {
// Create the list prompt // Create the list prompt
var prompt = new ListPrompt<T>(console, this); var prompt = new ListPrompt<T>(console, this);
var result = prompt.Show(_tree, PageSize); var result = await prompt.Show(_tree, cancellationToken, PageSize).ConfigureAwait(false);
// Return the selected item // Return the selected item
return result.Items[result.Index].Data; return result.Items[result.Index].Data;

View File

@ -5,6 +5,8 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -94,13 +96,19 @@ namespace Spectre.Console
/// <returns>The user input converted to the expected type.</returns> /// <returns>The user input converted to the expected type.</returns>
/// <inheritdoc/> /// <inheritdoc/>
public T Show(IAnsiConsole console) public T Show(IAnsiConsole console)
{
return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async Task<T> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
{ {
if (console is null) if (console is null)
{ {
throw new ArgumentNullException(nameof(console)); throw new ArgumentNullException(nameof(console));
} }
return console.RunExclusive(() => return await console.RunExclusive(async () =>
{ {
var promptStyle = PromptStyle ?? Style.Plain; var promptStyle = PromptStyle ?? Style.Plain;
var converter = Converter ?? TypeConverterHelper.ConvertToString; var converter = Converter ?? TypeConverterHelper.ConvertToString;
@ -111,7 +119,7 @@ namespace Spectre.Console
while (true) while (true)
{ {
var input = console.ReadLine(promptStyle, IsSecret, choices); var input = await console.ReadLine(promptStyle, IsSecret, choices, cancellationToken).ConfigureAwait(false);
// Nothing entered? // Nothing entered?
if (string.IsNullOrWhiteSpace(input)) if (string.IsNullOrWhiteSpace(input))
@ -162,7 +170,7 @@ namespace Spectre.Console
return result; return result;
} }
}); }).ConfigureAwait(false);
} }
/// <summary> /// <summary>