diff --git a/docs/input/live/index.cshtml b/docs/input/live/index.cshtml
new file mode 100644
index 0000000..d6b660a
--- /dev/null
+++ b/docs/input/live/index.cshtml
@@ -0,0 +1,12 @@
+Title: Live Displays
+Order: 4
+---
+
+
Sections
+
+
+@foreach (IDocument child in OutputPages.GetChildrenOf(Document))
+{
+ - @Html.DocumentLink(child)
+}
+
\ No newline at end of file
diff --git a/docs/input/progress.md b/docs/input/live/progress.md
similarity index 79%
rename from docs/input/progress.md
rename to docs/input/live/progress.md
index 6fe2978..369dd1e 100644
--- a/docs/input/progress.md
+++ b/docs/input/live/progress.md
@@ -1,16 +1,23 @@
Title: Progress
Order: 5
+RedirectFrom: progress
---
Spectre.Console can display information about long running tasks in the console.
-
+
+
+
+ The progress display is not
+ thread safe, and using it together with other interactive components such as
+ prompts, status displays or other progress displays are not supported.
+
If the current terminal isn't considered "interactive", such as when running
in a continuous integration system, or the terminal can't display
ANSI control sequence, any progress will be displayed in a simpler way.
-
+
# Usage
diff --git a/docs/input/status.md b/docs/input/live/status.md
similarity index 77%
rename from docs/input/status.md
rename to docs/input/live/status.md
index 8ad0307..1508c0e 100644
--- a/docs/input/status.md
+++ b/docs/input/live/status.md
@@ -1,10 +1,17 @@
Title: Status
Order: 6
+RedirectFrom: status
---
Spectre.Console can display information about long running tasks in the console.
-
+
+
+
+ The status display is not
+ thread safe, and using it together with other interactive components such as
+ prompts, progress displays or other status displays are not supported.
+
If the current terminal isn't considered "interactive", such as when running
in a continuous integration system, or the terminal can't display
diff --git a/docs/input/prompts/multiselection.md b/docs/input/prompts/multiselection.md
index d5ed617..0591cbe 100644
--- a/docs/input/prompts/multiselection.md
+++ b/docs/input/prompts/multiselection.md
@@ -7,6 +7,11 @@ one or many items from a provided list.
+
+ The use of prompts
+ insides status or progress displays is not supported.
+
+
# Usage
```csharp
diff --git a/docs/input/prompts/selection.md b/docs/input/prompts/selection.md
index 620b0e8..535919e 100644
--- a/docs/input/prompts/selection.md
+++ b/docs/input/prompts/selection.md
@@ -7,6 +7,11 @@ a single item from a provided list.
+
+ Using prompts inside
+ status or progress displays, are not supported.
+
+
# Usage
```csharp
diff --git a/docs/input/prompts/text.md b/docs/input/prompts/text.md
index 9fb65e7..59d0409 100644
--- a/docs/input/prompts/text.md
+++ b/docs/input/prompts/text.md
@@ -1,4 +1,4 @@
-Title: Text
+Title: Text prompt
Order: 0
RedirectFrom: prompt
---
@@ -6,6 +6,11 @@ RedirectFrom: prompt
Sometimes you want to get some input from the user, and for this
you can use the `Prompt`.
+
+ The use of prompts
+ insides status or progress displays is not supported.
+
+
# Confirmation
```csharp
diff --git a/dotnet-tools.json b/dotnet-tools.json
index 720fc54..86e2bc7 100644
--- a/dotnet-tools.json
+++ b/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"cake.tool": {
- "version": "1.0.0-rc0002",
+ "version": "1.1.0",
"commands": [
"dotnet-cake"
]
diff --git a/examples/Console/Prompt/Program.cs b/examples/Console/Prompt/Program.cs
index 2dc5127..0c716c2 100644
--- a/examples/Console/Prompt/Program.cs
+++ b/examples/Console/Prompt/Program.cs
@@ -59,6 +59,7 @@ namespace Cursor
new MultiSelectionPrompt()
.PageSize(10)
.Title("What are your [green]favorite fruits[/]?")
+ .MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.InstructionsText("[grey](Press [blue][/] to toggle a fruit, [green][/] to accept)[/]")
.AddChoices(new[]
{
@@ -75,6 +76,7 @@ namespace Cursor
fruit = AnsiConsole.Prompt(
new SelectionPrompt()
.Title("Ok, but if you could only choose [green]one[/]?")
+ .MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.AddChoices(favorites));
}
diff --git a/src/Spectre.Console.Testing/Fakes/FakeAnsiConsole.cs b/src/Spectre.Console.Testing/Fakes/FakeAnsiConsole.cs
index c692b20..1f3cab0 100644
--- a/src/Spectre.Console.Testing/Fakes/FakeAnsiConsole.cs
+++ b/src/Spectre.Console.Testing/Fakes/FakeAnsiConsole.cs
@@ -9,12 +9,14 @@ namespace Spectre.Console.Testing
{
private readonly StringWriter _writer;
private readonly IAnsiConsole _console;
+ private readonly FakeExclusivityMode _exclusivityLock;
public string Output => _writer.ToString();
public Profile Profile => _console.Profile;
public IAnsiConsoleCursor Cursor => _console.Cursor;
public FakeConsoleInput Input { get; }
+ public IExclusivityMode ExclusivityMode => _exclusivityLock;
public RenderPipeline Pipeline => _console.Pipeline;
IAnsiConsoleInput IAnsiConsole.Input => Input;
@@ -24,6 +26,7 @@ namespace Spectre.Console.Testing
AnsiSupport ansi = AnsiSupport.Yes,
int width = 80)
{
+ _exclusivityLock = new FakeExclusivityMode();
_writer = new StringWriter();
var factory = new AnsiConsoleFactory();
diff --git a/src/Spectre.Console.Testing/Fakes/FakeConsole.cs b/src/Spectre.Console.Testing/Fakes/FakeConsole.cs
index 183d916..0bd53cc 100644
--- a/src/Spectre.Console.Testing/Fakes/FakeConsole.cs
+++ b/src/Spectre.Console.Testing/Fakes/FakeConsole.cs
@@ -12,6 +12,7 @@ namespace Spectre.Console.Testing
public Profile Profile { get; }
public IAnsiConsoleCursor Cursor => new FakeAnsiConsoleCursor();
IAnsiConsoleInput IAnsiConsole.Input => Input;
+ public IExclusivityMode ExclusivityMode { get; }
public RenderPipeline Pipeline { get; }
public FakeConsoleInput Input { get; }
@@ -24,6 +25,7 @@ namespace Spectre.Console.Testing
bool legacyConsole = false, bool interactive = true)
{
Input = new FakeConsoleInput();
+ ExclusivityMode = new FakeExclusivityMode();
Pipeline = new RenderPipeline();
Profile = new Profile(new StringWriter(), encoding ?? Encoding.UTF8);
diff --git a/src/Spectre.Console.Testing/Fakes/FakeExclusivityMode.cs b/src/Spectre.Console.Testing/Fakes/FakeExclusivityMode.cs
new file mode 100644
index 0000000..ffb98e7
--- /dev/null
+++ b/src/Spectre.Console.Testing/Fakes/FakeExclusivityMode.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Spectre.Console.Testing
+{
+ public sealed class FakeExclusivityMode : IExclusivityMode
+ {
+ public T Run(Func func)
+ {
+ return func();
+ }
+
+ public async Task Run(Func> func)
+ {
+ return await func();
+ }
+ }
+}
diff --git a/src/Spectre.Console/AnsiConsoleFactory.cs b/src/Spectre.Console/AnsiConsoleFactory.cs
index 9b060a9..cb4d541 100644
--- a/src/Spectre.Console/AnsiConsoleFactory.cs
+++ b/src/Spectre.Console/AnsiConsoleFactory.cs
@@ -2,6 +2,7 @@ using System;
using System.Runtime.InteropServices;
using System.Text;
using Spectre.Console.Enrichment;
+using Spectre.Console.Internal;
namespace Spectre.Console
{
@@ -58,7 +59,9 @@ namespace Spectre.Console
settings.Enrichment,
settings.EnvironmentVariables);
- return new AnsiConsoleFacade(profile);
+ return new AnsiConsoleFacade(
+ profile,
+ settings.ExclusivityMode ?? new DefaultExclusivityMode());
}
private static (bool Ansi, bool Legacy) DetectAnsi(AnsiConsoleSettings settings, System.IO.TextWriter buffer)
diff --git a/src/Spectre.Console/AnsiConsoleSettings.cs b/src/Spectre.Console/AnsiConsoleSettings.cs
index 2027872..e847481 100644
--- a/src/Spectre.Console/AnsiConsoleSettings.cs
+++ b/src/Spectre.Console/AnsiConsoleSettings.cs
@@ -30,6 +30,11 @@ namespace Spectre.Console
///
public InteractionSupport Interactive { get; set; }
+ ///
+ /// Gets or sets the exclusivity mode.
+ ///
+ public IExclusivityMode? ExclusivityMode { get; set; }
+
///
/// Gets or sets the profile enrichments settings.
///
diff --git a/src/Spectre.Console/Cli/FlagValue.cs b/src/Spectre.Console/Cli/FlagValue.cs
index a1989bc..9ae8845 100644
--- a/src/Spectre.Console/Cli/FlagValue.cs
+++ b/src/Spectre.Console/Cli/FlagValue.cs
@@ -31,7 +31,9 @@ namespace Spectre.Console.Cli
set
{
#pragma warning disable CS8601 // Possible null reference assignment.
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Value = (T)value;
+#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8601 // Possible null reference assignment.
}
}
diff --git a/src/Spectre.Console/Cli/Internal/Collections/MultiMap.cs b/src/Spectre.Console/Cli/Internal/Collections/MultiMap.cs
index 4a249fd..03fe449 100644
--- a/src/Spectre.Console/Cli/Internal/Collections/MultiMap.cs
+++ b/src/Spectre.Console/Cli/Internal/Collections/MultiMap.cs
@@ -165,7 +165,9 @@ namespace Spectre.Console.Cli
if (pair.Key != null)
{
#pragma warning disable CS8604 // Possible null reference argument of value.
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Add((TKey)pair.Key, (TValue)pair.Value);
+#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8604 // Possible null reference argument of value.
}
}
diff --git a/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs
new file mode 100644
index 0000000..10ba8e6
--- /dev/null
+++ b/src/Spectre.Console/Extensions/AnsiConsoleExtensions.Exclusive.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Spectre.Console
+{
+ ///
+ /// Contains extension methods for .
+ ///
+ public static partial class AnsiConsoleExtensions
+ {
+ ///
+ /// Runs the specified function in exclusive mode.
+ ///
+ /// The result type.
+ /// The console.
+ /// The func to run in exclusive mode.
+ /// The result of the function.
+ public static T RunExclusive(this IAnsiConsole console, Func func)
+ {
+ return console.ExclusivityMode.Run(func);
+ }
+
+ ///
+ /// Runs the specified function in exclusive mode asynchronously.
+ ///
+ /// The result type.
+ /// The console.
+ /// The func to run in exclusive mode.
+ /// The result of the function.
+ public static Task RunExclusive(this IAnsiConsole console, Func> func)
+ {
+ return console.ExclusivityMode.Run(func);
+ }
+ }
+}
diff --git a/src/Spectre.Console/IAnsiConsole.cs b/src/Spectre.Console/IAnsiConsole.cs
index e854fa1..8e3e7ed 100644
--- a/src/Spectre.Console/IAnsiConsole.cs
+++ b/src/Spectre.Console/IAnsiConsole.cs
@@ -23,6 +23,11 @@ namespace Spectre.Console
///
IAnsiConsoleInput Input { get; }
+ ///
+ /// Gets the exclusivity mode.
+ ///
+ IExclusivityMode ExclusivityMode { get; }
+
///
/// Gets the render pipeline.
///
diff --git a/src/Spectre.Console/IExclusivityMode.cs b/src/Spectre.Console/IExclusivityMode.cs
new file mode 100644
index 0000000..fae0c41
--- /dev/null
+++ b/src/Spectre.Console/IExclusivityMode.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Spectre.Console
+{
+ ///
+ /// Represents an exclusivity mode.
+ ///
+ public interface IExclusivityMode
+ {
+ ///
+ /// Runs the specified function in exclusive mode.
+ ///
+ /// The result type.
+ /// The func to run in exclusive mode.
+ /// The result of the function.
+ T Run(Func func);
+
+ ///
+ /// Runs the specified function in exclusive mode asynchronously.
+ ///
+ /// The result type.
+ /// The func to run in exclusive mode.
+ /// The result of the function.
+ Task Run(Func> func);
+ }
+}
diff --git a/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs b/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
index d01e5ae..7e41b13 100644
--- a/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
+++ b/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
@@ -13,9 +13,10 @@ namespace Spectre.Console
public Profile Profile { get; }
public IAnsiConsoleCursor Cursor => GetBackend().Cursor;
public IAnsiConsoleInput Input { get; }
+ public IExclusivityMode ExclusivityMode { get; }
public RenderPipeline Pipeline { get; }
- public AnsiConsoleFacade(Profile profile)
+ public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode)
{
_renderLock = new object();
_ansiBackend = new AnsiConsoleBackend(profile);
@@ -23,6 +24,7 @@ namespace Spectre.Console
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
Input = new DefaultInput(Profile);
+ ExclusivityMode = exclusivityMode ?? throw new ArgumentNullException(nameof(exclusivityMode));
Pipeline = new RenderPipeline();
}
diff --git a/src/Spectre.Console/Internal/Cell.cs b/src/Spectre.Console/Internal/Cell.cs
index c904757..5d0905c 100644
--- a/src/Spectre.Console/Internal/Cell.cs
+++ b/src/Spectre.Console/Internal/Cell.cs
@@ -1,4 +1,3 @@
-using System.Linq;
using Spectre.Console.Rendering;
using Wcwidth;
diff --git a/src/Spectre.Console/Internal/DefaultExclusivityMode.cs b/src/Spectre.Console/Internal/DefaultExclusivityMode.cs
new file mode 100644
index 0000000..3b931f8
--- /dev/null
+++ b/src/Spectre.Console/Internal/DefaultExclusivityMode.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Spectre.Console.Internal
+{
+ internal sealed class DefaultExclusivityMode : IExclusivityMode
+ {
+ private static readonly SemaphoreSlim _semaphore;
+
+ static DefaultExclusivityMode()
+ {
+ _semaphore = new SemaphoreSlim(1, 1);
+ }
+
+ public T Run(Func func)
+ {
+ // Try aquiring the exclusivity semaphore
+ if (!_semaphore.Wait(0))
+ {
+ throw new InvalidOperationException(
+ "Trying to run one or more interactive functions concurrently. " +
+ "Operations with dynamic displays (e.g. a prompt and a progress display) " +
+ "cannot be running at the same time.");
+ }
+
+ try
+ {
+ return func();
+ }
+ finally
+ {
+ _semaphore.Release(1);
+ }
+ }
+
+ public async Task Run(Func> func)
+ {
+ // Try aquiring the exclusivity semaphore
+ if (!await _semaphore.WaitAsync(0).ConfigureAwait(false))
+ {
+ // TODO: Need a better message here
+ throw new InvalidOperationException(
+ "Could not aquire the interactive semaphore");
+ }
+
+ try
+ {
+ return await func();
+ }
+ finally
+ {
+ _semaphore.Release(1);
+ }
+ }
+ }
+}
diff --git a/src/Spectre.Console/Recorder.cs b/src/Spectre.Console/Recorder.cs
index 5893b4b..4ffca3c 100644
--- a/src/Spectre.Console/Recorder.cs
+++ b/src/Spectre.Console/Recorder.cs
@@ -23,6 +23,9 @@ namespace Spectre.Console
///
public IAnsiConsoleInput Input => _console.Input;
+ ///
+ public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
+
///
public RenderPipeline Pipeline => _console.Pipeline;
diff --git a/src/Spectre.Console/Widgets/Progress/Progress.cs b/src/Spectre.Console/Widgets/Progress/Progress.cs
index 8fc1e91..7a9f12e 100644
--- a/src/Spectre.Console/Widgets/Progress/Progress.cs
+++ b/src/Spectre.Console/Widgets/Progress/Progress.cs
@@ -118,38 +118,41 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(action));
}
- var renderer = CreateRenderer();
- renderer.Started();
-
- T result;
-
- try
+ return await _console.RunExclusive(async () =>
{
- using (new RenderHookScope(_console, renderer))
- {
- var context = new ProgressContext(_console, renderer);
+ var renderer = CreateRenderer();
+ renderer.Started();
- if (AutoRefresh)
+ T result;
+
+ try
+ {
+ using (new RenderHookScope(_console, renderer))
{
- using (var thread = new ProgressRefreshThread(context, renderer.RefreshRate))
+ var context = new ProgressContext(_console, renderer);
+
+ if (AutoRefresh)
+ {
+ using (var thread = new ProgressRefreshThread(context, renderer.RefreshRate))
+ {
+ result = await action(context).ConfigureAwait(false);
+ }
+ }
+ else
{
result = await action(context).ConfigureAwait(false);
}
- }
- else
- {
- result = await action(context).ConfigureAwait(false);
- }
- context.Refresh();
+ context.Refresh();
+ }
+ }
+ finally
+ {
+ renderer.Completed(AutoClear);
}
- }
- finally
- {
- renderer.Completed(AutoClear);
- }
- return result;
+ return result;
+ }).ConfigureAwait(false);
}
private ProgressRenderer CreateRenderer()
diff --git a/src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs b/src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs
index 260c195..18696f9 100644
--- a/src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs
+++ b/src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs
@@ -72,6 +72,11 @@ namespace Spectre.Console
///
public List Show(IAnsiConsole console)
{
+ if (console is null)
+ {
+ throw new ArgumentNullException(nameof(console));
+ }
+
if (!console.Profile.Capabilities.Interactive)
{
throw new NotSupportedException(
@@ -86,50 +91,53 @@ namespace Spectre.Console
"terminal does not support ANSI escape sequences.");
}
- var converter = Converter ?? TypeConverterHelper.ConvertToString;
- var list = new RenderableMultiSelectionList(
- console, Title, PageSize, Choices,
- Selected, converter, HighlightStyle,
- MoreChoicesText, InstructionsText);
-
- using (new RenderHookScope(console, list))
+ return console.RunExclusive(() =>
{
- console.Cursor.Hide();
- list.Redraw();
+ var converter = Converter ?? TypeConverterHelper.ConvertToString;
+ var list = new RenderableMultiSelectionList(
+ console, Title, PageSize, Choices,
+ Selected, converter, HighlightStyle,
+ MoreChoicesText, InstructionsText);
- while (true)
+ using (new RenderHookScope(console, list))
{
- var key = console.Input.ReadKey(true);
- if (key.Key == ConsoleKey.Enter)
+ console.Cursor.Hide();
+ list.Redraw();
+
+ while (true)
{
- if (Required && list.Selections.Count == 0)
+ var key = console.Input.ReadKey(true);
+ if (key.Key == ConsoleKey.Enter)
{
+ if (Required && list.Selections.Count == 0)
+ {
+ continue;
+ }
+
+ break;
+ }
+
+ if (key.Key == ConsoleKey.Spacebar)
+ {
+ list.Select();
+ list.Redraw();
continue;
}
- break;
- }
-
- if (key.Key == ConsoleKey.Spacebar)
- {
- list.Select();
- list.Redraw();
- continue;
- }
-
- if (list.Update(key.Key))
- {
- list.Redraw();
+ if (list.Update(key.Key))
+ {
+ list.Redraw();
+ }
}
}
- }
- list.Clear();
- console.Cursor.Show();
+ list.Clear();
+ console.Cursor.Show();
- return list.Selections
- .Select(index => Choices[index])
- .ToList();
+ return list.Selections
+ .Select(index => Choices[index])
+ .ToList();
+ });
}
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs b/src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs
index 81c8f25..3c6672c 100644
--- a/src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs
+++ b/src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs
@@ -68,35 +68,38 @@ namespace Spectre.Console
"terminal does not support ANSI escape sequences.");
}
- var converter = Converter ?? TypeConverterHelper.ConvertToString;
- var list = new RenderableSelectionList(
- console, Title, PageSize, Choices,
- converter, HighlightStyle, MoreChoicesText);
-
- using (new RenderHookScope(console, list))
+ return console.RunExclusive(() =>
{
- console.Cursor.Hide();
- list.Redraw();
+ var converter = Converter ?? TypeConverterHelper.ConvertToString;
+ var list = new RenderableSelectionList(
+ console, Title, PageSize, Choices,
+ converter, HighlightStyle, MoreChoicesText);
- while (true)
+ using (new RenderHookScope(console, list))
{
- var key = console.Input.ReadKey(true);
- if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Spacebar)
- {
- break;
- }
+ console.Cursor.Hide();
+ list.Redraw();
- if (list.Update(key.Key))
+ while (true)
{
- list.Redraw();
+ var key = console.Input.ReadKey(true);
+ if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Spacebar)
+ {
+ break;
+ }
+
+ if (list.Update(key.Key))
+ {
+ list.Redraw();
+ }
}
}
- }
- list.Clear();
- console.Cursor.Show();
+ list.Clear();
+ console.Cursor.Show();
- return Choices[list.Index];
+ return Choices[list.Index];
+ });
}
}
}
diff --git a/src/Spectre.Console/Widgets/Prompt/TextPrompt.cs b/src/Spectre.Console/Widgets/Prompt/TextPrompt.cs
index 45021bb..b570781 100644
--- a/src/Spectre.Console/Widgets/Prompt/TextPrompt.cs
+++ b/src/Spectre.Console/Widgets/Prompt/TextPrompt.cs
@@ -100,66 +100,69 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(console));
}
- var promptStyle = PromptStyle ?? Style.Plain;
- var converter = Converter ?? TypeConverterHelper.ConvertToString;
- var choices = Choices.Select(choice => converter(choice)).ToList();
- var choiceMap = Choices.ToDictionary(choice => converter(choice), choice => choice, _comparer);
-
- WritePrompt(console);
-
- while (true)
+ return console.RunExclusive(() =>
{
- var input = console.ReadLine(promptStyle, IsSecret, choices);
+ var promptStyle = PromptStyle ?? Style.Plain;
+ var converter = Converter ?? TypeConverterHelper.ConvertToString;
+ var choices = Choices.Select(choice => converter(choice)).ToList();
+ var choiceMap = Choices.ToDictionary(choice => converter(choice), choice => choice, _comparer);
- // Nothing entered?
- if (string.IsNullOrWhiteSpace(input))
+ WritePrompt(console);
+
+ while (true)
{
- if (DefaultValue != null)
+ var input = console.ReadLine(promptStyle, IsSecret, choices);
+
+ // Nothing entered?
+ if (string.IsNullOrWhiteSpace(input))
{
- console.Write(IsSecret ? "******" : converter(DefaultValue.Value), promptStyle);
- console.WriteLine();
- return DefaultValue.Value;
+ if (DefaultValue != null)
+ {
+ console.Write(IsSecret ? "******" : converter(DefaultValue.Value), promptStyle);
+ console.WriteLine();
+ return DefaultValue.Value;
+ }
+
+ if (!AllowEmpty)
+ {
+ continue;
+ }
}
- if (!AllowEmpty)
- {
- continue;
- }
- }
+ console.WriteLine();
- console.WriteLine();
-
- T? result;
- if (Choices.Count > 0)
- {
- if (choiceMap.TryGetValue(input, out result) && result != null)
+ T? result;
+ if (Choices.Count > 0)
{
- return result;
+ if (choiceMap.TryGetValue(input, out result) && result != null)
+ {
+ return result;
+ }
+ else
+ {
+ console.MarkupLine(InvalidChoiceMessage);
+ WritePrompt(console);
+ continue;
+ }
}
- else
+ else if (!TypeConverterHelper.TryConvertFromString(input, out result) || result == null)
{
- console.MarkupLine(InvalidChoiceMessage);
+ console.MarkupLine(ValidationErrorMessage);
WritePrompt(console);
continue;
}
- }
- else if (!TypeConverterHelper.TryConvertFromString(input, out result) || result == null)
- {
- console.MarkupLine(ValidationErrorMessage);
- WritePrompt(console);
- continue;
- }
- // Run all validators
- if (!ValidateResult(result, out var validationMessage))
- {
- console.MarkupLine(validationMessage);
- WritePrompt(console);
- continue;
- }
+ // Run all validators
+ if (!ValidateResult(result, out var validationMessage))
+ {
+ console.MarkupLine(validationMessage);
+ WritePrompt(console);
+ continue;
+ }
- return result;
- }
+ return result;
+ }
+ });
}
///