diff --git a/docs/input/prompts/multiselection.md b/docs/input/prompts/multiselection.md
index 4e11b2613..90551c075 100644
--- a/docs/input/prompts/multiselection.md
+++ b/docs/input/prompts/multiselection.md
@@ -5,6 +5,9 @@ Highlights:
- Display multiple items for a user to scroll and choose from.
- Custom page sizes.
- Provide groups of selectable items.
+Reference:
+ - T:Spectre.Console.MultiSelectionPrompt`1
+ - M:Spectre.Console.AnsiConsole.Prompt``1(Spectre.Console.IPrompt{``0},System.Threading.CancellationToken)
---
The `MultiSelectionPrompt` can be used when you want the user to select
diff --git a/docs/input/prompts/selection.md b/docs/input/prompts/selection.md
index 150b32806..83fac7177 100644
--- a/docs/input/prompts/selection.md
+++ b/docs/input/prompts/selection.md
@@ -3,7 +3,7 @@ Order: 1
Description: "The **SelectionPrompt** can be used when you want the user to select a single item from a provided list."
Reference:
- T:Spectre.Console.SelectionPrompt`1
- - M:Spectre.Console.AnsiConsole.Prompt``1(Spectre.Console.IPrompt{``0})
+ - M:Spectre.Console.AnsiConsole.Prompt``1(Spectre.Console.IPrompt{``0},System.Threading.CancellationToken)
---
The `SelectionPrompt` can be used when you want the user to select
diff --git a/src/Spectre.Console.Testing/TestConsoleInput.cs b/src/Spectre.Console.Testing/TestConsoleInput.cs
index e471d30ed..8c8a0f364 100644
--- a/src/Spectre.Console.Testing/TestConsoleInput.cs
+++ b/src/Spectre.Console.Testing/TestConsoleInput.cs
@@ -77,18 +77,21 @@ public bool IsKeyAvailable()
}
///
- public ConsoleKeyInfo? ReadKey(bool intercept)
+ public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken = default)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
+ cancellationToken.ThrowIfCancellationRequested();
+
return _input.Dequeue();
}
///
- public Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
+ [Obsolete("This method will be removed in a future release. Use the synchronous ReadKey() method instead.", error: false)]
+ public Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
}
diff --git a/src/Spectre.Console/AnsiConsole.Prompt.cs b/src/Spectre.Console/AnsiConsole.Prompt.cs
index ef892ae9e..266b079f4 100644
--- a/src/Spectre.Console/AnsiConsole.Prompt.cs
+++ b/src/Spectre.Console/AnsiConsole.Prompt.cs
@@ -10,15 +10,16 @@ public static partial class AnsiConsole
///
/// The prompt result type.
/// The prompt to display.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Prompt(IPrompt prompt)
+ public static T Prompt(IPrompt prompt, CancellationToken cancellationToken = default)
{
if (prompt is null)
{
throw new ArgumentNullException(nameof(prompt));
}
- return prompt.Show(Console);
+ return prompt.Show(Console, cancellationToken);
}
///
@@ -26,10 +27,11 @@ public static T Prompt(IPrompt prompt)
///
/// The prompt result type.
/// The prompt markup text.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Ask(string prompt)
+ public static T Ask(string prompt, CancellationToken cancellationToken = default)
{
- return new TextPrompt(prompt).Show(Console);
+ return new TextPrompt(prompt).Show(Console, cancellationToken);
}
///
@@ -38,12 +40,13 @@ public static T Ask(string prompt)
/// The prompt result type.
/// The prompt markup text.
/// The default value.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Ask(string prompt, T defaultValue)
+ public static T Ask(string prompt, T defaultValue, CancellationToken cancellationToken = default)
{
return new TextPrompt(prompt)
.DefaultValue(defaultValue)
- .Show(Console);
+ .Show(Console, cancellationToken);
}
///
@@ -51,13 +54,14 @@ public static T Ask(string prompt, T defaultValue)
///
/// The prompt markup text.
/// Specifies the default answer.
+ /// The token to monitor for cancellation requests.
/// true if the user selected "yes", otherwise false.
- public static bool Confirm(string prompt, bool defaultValue = true)
+ public static bool Confirm(string prompt, bool defaultValue = true, CancellationToken cancellationToken = default)
{
return new ConfirmationPrompt(prompt)
{
DefaultValue = defaultValue,
}
- .Show(Console);
+ .Show(Console, cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Input.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Input.cs
index 2d6a4cb0b..cc3f1e847 100644
--- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Input.cs
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Input.cs
@@ -5,7 +5,7 @@ namespace Spectre.Console;
///
public static partial class AnsiConsoleExtensions
{
- internal static async Task ReadLine(this IAnsiConsole console, Style? style, bool secret, char? mask, IEnumerable? items = null, CancellationToken cancellationToken = default)
+ internal static string ReadLine(this IAnsiConsole console, Style? style, bool secret, char? mask, IEnumerable? items = null, CancellationToken cancellationToken = default)
{
if (console is null)
{
@@ -19,14 +19,7 @@ internal static async Task ReadLine(this IAnsiConsole console, Style? st
while (true)
{
- cancellationToken.ThrowIfCancellationRequested();
- var rawKey = await console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
- if (rawKey == null)
- {
- continue;
- }
-
- var key = rawKey.Value;
+ var key = console.Input.ReadKey(true, cancellationToken);
if (key.Key == ConsoleKey.Enter)
{
return text;
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs
index 382503f8b..002704d1f 100644
--- a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Prompt.cs
@@ -11,15 +11,16 @@ public static partial class AnsiConsoleExtensions
/// The prompt result type.
/// The console.
/// The prompt to display.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Prompt(this IAnsiConsole console, IPrompt prompt)
- {
+ public static T Prompt(this IAnsiConsole console, IPrompt prompt, CancellationToken cancellationToken = default)
+ {
if (prompt is null)
{
throw new ArgumentNullException(nameof(prompt));
}
- return prompt.Show(console);
+ return prompt.Show(console, cancellationToken);
}
///
@@ -28,10 +29,11 @@ public static T Prompt(this IAnsiConsole console, IPrompt prompt)
/// The prompt result type.
/// The console.
/// The prompt markup text.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Ask(this IAnsiConsole console, string prompt)
+ public static T Ask(this IAnsiConsole console, string prompt, CancellationToken cancellationToken = default)
{
- return new TextPrompt(prompt).Show(console);
+ return new TextPrompt(prompt).Show(console, cancellationToken);
}
///
@@ -41,12 +43,13 @@ public static T Ask(this IAnsiConsole console, string prompt)
/// The console.
/// The prompt markup text.
/// Specific CultureInfo to use when converting input.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- public static T Ask(this IAnsiConsole console, string prompt, CultureInfo? culture)
+ public static T Ask(this IAnsiConsole console, string prompt, CultureInfo? culture, CancellationToken cancellationToken = default)
{
var textPrompt = new TextPrompt(prompt);
textPrompt.Culture = culture;
- return textPrompt.Show(console);
+ return textPrompt.Show(console, cancellationToken);
}
///
@@ -55,13 +58,14 @@ public static T Ask(this IAnsiConsole console, string prompt, CultureInfo? cu
/// The console.
/// The prompt markup text.
/// Specifies the default answer.
+ /// The token to monitor for cancellation requests.
/// true if the user selected "yes", otherwise false.
- public static bool Confirm(this IAnsiConsole console, string prompt, bool defaultValue = true)
+ public static bool Confirm(this IAnsiConsole console, string prompt, bool defaultValue = true, CancellationToken cancellationToken = default)
{
return new ConfirmationPrompt(prompt)
{
DefaultValue = defaultValue,
}
- .Show(console);
+ .Show(console, cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console/IAnsiConsoleInput.cs b/src/Spectre.Console/IAnsiConsoleInput.cs
index 9bc5a12cb..7e55b394e 100644
--- a/src/Spectre.Console/IAnsiConsoleInput.cs
+++ b/src/Spectre.Console/IAnsiConsoleInput.cs
@@ -15,15 +15,23 @@ public interface IAnsiConsoleInput
///
/// Reads a key from the console.
///
- /// Whether or not to intercept the key.
+ ///
+ /// Determines whether to display the pressed key in the console window.
+ /// to not display the pressed key; otherwise, .
+ ///
+ /// The token to monitor for cancellation requests.
/// The key that was read.
- ConsoleKeyInfo? ReadKey(bool intercept);
+ ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken = default);
///
/// Reads a key from the console.
///
- /// Whether or not to intercept the key.
+ ///
+ /// Determines whether to display the pressed key in the console window.
+ /// to not display the pressed key; otherwise, .
+ ///
/// The token to monitor for cancellation requests.
/// The key that was read.
- Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken);
+ [Obsolete("This method will be removed in a future release. Use the synchronous ReadKey() method instead.", error: false)]
+ Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/DefaultInput.cs b/src/Spectre.Console/Internal/DefaultInput.cs
index 423285c80..fb3af265e 100644
--- a/src/Spectre.Console/Internal/DefaultInput.cs
+++ b/src/Spectre.Console/Internal/DefaultInput.cs
@@ -19,38 +19,22 @@ public bool IsKeyAvailable()
return System.Console.KeyAvailable;
}
- public ConsoleKeyInfo? ReadKey(bool intercept)
+ public ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken)
{
- if (!_profile.Capabilities.Interactive)
+ cancellationToken.ThrowIfCancellationRequested();
+
+ while (!IsKeyAvailable())
{
- throw new InvalidOperationException("Failed to read input in non-interactive mode.");
+ cancellationToken.ThrowIfCancellationRequested();
+ Thread.Sleep(5);
}
return System.Console.ReadKey(intercept);
}
- public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
+ [Obsolete("This method will be removed in a future release. Use the synchronous ReadKey() method instead.", error: true)]
+ public 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)
- {
- return null;
- }
-
- if (System.Console.KeyAvailable)
- {
- break;
- }
-
- await Task.Delay(5, cancellationToken).ConfigureAwait(false);
- }
-
- return ReadKey(intercept);
+ return Task.FromResult(ReadKey(intercept, cancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Prompts/ConfirmationPrompt.cs b/src/Spectre.Console/Prompts/ConfirmationPrompt.cs
index 5257d5233..b108f6c84 100644
--- a/src/Spectre.Console/Prompts/ConfirmationPrompt.cs
+++ b/src/Spectre.Console/Prompts/ConfirmationPrompt.cs
@@ -68,13 +68,7 @@ public ConfirmationPrompt(string prompt)
}
///
- public bool Show(IAnsiConsole console)
- {
- return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
- }
-
- ///
- public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ public bool Show(IAnsiConsole console, CancellationToken cancellationToken = default)
{
var comparer = Comparer ?? StringComparer.CurrentCultureIgnoreCase;
@@ -89,8 +83,15 @@ public async Task ShowAsync(IAnsiConsole console, CancellationToken cancel
.AddChoice(Yes)
.AddChoice(No);
- var result = await prompt.ShowAsync(console, cancellationToken).ConfigureAwait(false);
+ var result = prompt.Show(console, cancellationToken);
return comparer.Compare(Yes.ToString(), result.ToString()) == 0;
}
+
+ ///
+ [Obsolete("This method will be removed in a future release. Use the synchronous Show() method instead.", error: false)]
+ public Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(Show(console, cancellationToken));
+ }
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Prompts/IPrompt.cs b/src/Spectre.Console/Prompts/IPrompt.cs
index 36ed44c46..9e0602a9f 100644
--- a/src/Spectre.Console/Prompts/IPrompt.cs
+++ b/src/Spectre.Console/Prompts/IPrompt.cs
@@ -10,8 +10,9 @@ public interface IPrompt
/// Shows the prompt.
///
/// The console.
+ /// The token to monitor for cancellation requests.
/// The prompt input result.
- T Show(IAnsiConsole console);
+ T Show(IAnsiConsole console, CancellationToken cancellationToken = default);
///
/// Shows the prompt asynchronously.
@@ -19,5 +20,6 @@ public interface IPrompt
/// The console.
/// The token to monitor for cancellation requests.
/// The prompt input result.
+ [Obsolete("This method will be removed in a future release. Use the synchronous Show() method instead.", error: false)]
Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Prompts/List/ListPrompt.cs b/src/Spectre.Console/Prompts/List/ListPrompt.cs
index ca0fa3fee..cd90bfb08 100644
--- a/src/Spectre.Console/Prompts/List/ListPrompt.cs
+++ b/src/Spectre.Console/Prompts/List/ListPrompt.cs
@@ -12,7 +12,7 @@ public ListPrompt(IAnsiConsole console, IListPromptStrategy strategy)
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
}
- public async Task> Show(
+ public ListPromptState Show(
ListPromptTree tree,
Func converter,
SelectionMode selectionMode,
@@ -57,14 +57,7 @@ public async Task> Show(
while (true)
{
- cancellationToken.ThrowIfCancellationRequested();
- var rawKey = await _console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
- if (rawKey == null)
- {
- continue;
- }
-
- var key = rawKey.Value;
+ var key = _console.Input.ReadKey(true, cancellationToken);
var result = _strategy.HandleInput(key, state);
if (result == ListPromptInputResult.Submit)
{
diff --git a/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs b/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
index ffc3f4e58..32c8f3a9c 100644
--- a/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
+++ b/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
@@ -84,18 +84,12 @@ public IMultiSelectionItem AddChoice(T item)
}
///
- public List Show(IAnsiConsole console)
- {
- return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
- }
-
- ///
- public async Task> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ public List Show(IAnsiConsole console, CancellationToken cancellationToken = default)
{
// Create the list prompt
var prompt = new ListPrompt(console, this);
var converter = Converter ?? TypeConverterHelper.ConvertToString;
- var result = await prompt.Show(Tree, converter, Mode, false, false, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
+ var result = prompt.Show(Tree, converter, Mode, false, false, PageSize, WrapAround, cancellationToken);
if (Mode == SelectionMode.Leaf)
{
@@ -111,6 +105,13 @@ public async Task> ShowAsync(IAnsiConsole console, CancellationToken can
.ToList();
}
+ ///
+ [Obsolete("This method will be removed in a future release. Use the synchronous Show() method instead.", error: false)]
+ public Task> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(Show(console, cancellationToken));
+ }
+
///
/// Returns all parent items of the given .
///
diff --git a/src/Spectre.Console/Prompts/SelectionPrompt.cs b/src/Spectre.Console/Prompts/SelectionPrompt.cs
index c9f55690e..3818a69df 100644
--- a/src/Spectre.Console/Prompts/SelectionPrompt.cs
+++ b/src/Spectre.Console/Prompts/SelectionPrompt.cs
@@ -89,23 +89,24 @@ public ISelectionItem AddChoice(T item)
}
///
- public T Show(IAnsiConsole console)
- {
- return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
- }
-
- ///
- public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ public T Show(IAnsiConsole console, CancellationToken cancellationToken = default)
{
// Create the list prompt
var prompt = new ListPrompt(console, this);
var converter = Converter ?? TypeConverterHelper.ConvertToString;
- var result = await prompt.Show(_tree, converter, Mode, true, SearchEnabled, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
+ var result = prompt.Show(_tree, converter, Mode, true, SearchEnabled, PageSize, WrapAround, cancellationToken);
// Return the selected item
return result.Items[result.Index].Data;
}
+ ///
+ [Obsolete("This method will be removed in a future release. Use the synchronous Show() method instead.", error: false)]
+ public Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(Show(console, cancellationToken));
+ }
+
///
ListPromptInputResult IListPromptStrategy.HandleInput(ConsoleKeyInfo key, ListPromptState state)
{
diff --git a/src/Spectre.Console/Prompts/TextPrompt.cs b/src/Spectre.Console/Prompts/TextPrompt.cs
index 9574df175..745edf2c2 100644
--- a/src/Spectre.Console/Prompts/TextPrompt.cs
+++ b/src/Spectre.Console/Prompts/TextPrompt.cs
@@ -104,22 +104,17 @@ public TextPrompt(string prompt, StringComparer? comparer = null)
/// Shows the prompt and requests input from the user.
///
/// The console to show the prompt in.
+ /// The token to monitor for cancellation requests.
/// The user input converted to the expected type.
///
- public T Show(IAnsiConsole console)
- {
- return ShowAsync(console, CancellationToken.None).GetAwaiter().GetResult();
- }
-
- ///
- public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ public T Show(IAnsiConsole console, CancellationToken cancellationToken = default)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
- return await console.RunExclusive(async () =>
+ return console.RunExclusive(() =>
{
var promptStyle = PromptStyle ?? Style.Plain;
var converter = Converter ?? TypeConverterHelper.ConvertToString;
@@ -130,7 +125,7 @@ public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellat
while (true)
{
- var input = await console.ReadLine(promptStyle, IsSecret, Mask, choices, cancellationToken).ConfigureAwait(false);
+ var input = console.ReadLine(promptStyle, IsSecret, Mask, choices, cancellationToken);
// Nothing entered?
if (string.IsNullOrWhiteSpace(input))
@@ -182,7 +177,14 @@ public async Task ShowAsync(IAnsiConsole console, CancellationToken cancellat
return result;
}
- }).ConfigureAwait(false);
+ });
+ }
+
+ ///
+ [Obsolete("This method will be removed in a future release. Use the synchronous Show() method instead.", error: false)]
+ public Task ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(Show(console, cancellationToken));
}
///
diff --git a/src/Tests/Spectre.Console.Tests/Unit/Prompts/CancellationTests.cs b/src/Tests/Spectre.Console.Tests/Unit/Prompts/CancellationTests.cs
new file mode 100644
index 000000000..6b206e424
--- /dev/null
+++ b/src/Tests/Spectre.Console.Tests/Unit/Prompts/CancellationTests.cs
@@ -0,0 +1,60 @@
+namespace Spectre.Console.Tests.Unit;
+
+public class CancellationTests
+{
+ private readonly IAnsiConsole _console = AnsiConsole.Create(new AnsiConsoleSettings { Interactive = InteractionSupport.Yes, Ansi = AnsiSupport.Yes });
+
+ [Fact]
+ public void ConfirmationPrompt_Should_Support_Cancellation()
+ {
+ // Given
+ var prompt = new ConfirmationPrompt("");
+
+ // When
+ Action action = () => prompt.Show(_console, new CancellationToken(canceled: true));
+
+ // Then
+ action.ShouldThrow();
+ }
+
+ [Fact]
+ public void TextPrompt_Should_Support_Cancellation()
+ {
+ // Given
+ var prompt = new TextPrompt("");
+
+ // When
+ Action action = () => prompt.Show(_console, new CancellationToken(canceled: true));
+
+ // Then
+ action.ShouldThrow();
+ }
+
+ [Fact]
+ public void MultiSelectionPrompt_Should_Support_Cancellation()
+ {
+ // Given
+ var prompt = new MultiSelectionPrompt();
+ prompt.AddChoice("");
+
+ // When
+ Action action = () => prompt.Show(_console, new CancellationToken(canceled: true));
+
+ // Then
+ action.ShouldThrow();
+ }
+
+ [Fact]
+ public void SelectionPrompt_Should_Support_Cancellation()
+ {
+ // Given
+ var prompt = new SelectionPrompt();
+ prompt.AddChoice("");
+
+ // When
+ Action action = () => prompt.Show(_console, new CancellationToken(canceled: true));
+
+ // Then
+ action.ShouldThrow();
+ }
+}
\ No newline at end of file