mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02:50 +08:00
Add support for exclusive mode
This commit is contained in:
parent
c2bab0ebf8
commit
7f6f2437b1
12
docs/input/live/index.cshtml
Normal file
12
docs/input/live/index.cshtml
Normal file
@ -0,0 +1,12 @@
|
||||
Title: Live Displays
|
||||
Order: 4
|
||||
---
|
||||
|
||||
<h1>Sections</h1>
|
||||
|
||||
<ul>
|
||||
@foreach (IDocument child in OutputPages.GetChildrenOf(Document))
|
||||
{
|
||||
<li>@Html.DocumentLink(child)</li>
|
||||
}
|
||||
</ul>
|
@ -1,16 +1,23 @@
|
||||
Title: Progress
|
||||
Order: 5
|
||||
RedirectFrom: progress
|
||||
---
|
||||
|
||||
Spectre.Console can display information about long running tasks in the console.
|
||||
|
||||
<img src="assets/images/progress.png" style="max-width: 100%;margin-bottom:20px;">
|
||||
<img src="../assets/images/progress.png" style="max-width: 100%;margin-bottom:20px;">
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle icon-web"></i> 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.
|
||||
</div>
|
||||
|
||||
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.
|
||||
|
||||
<img src="assets/images/progress_fallback.png" style="max-width: 100%;">
|
||||
<img src="../assets/images/progress_fallback.png" style="max-width: 100%;">
|
||||
|
||||
# Usage
|
||||
|
@ -1,10 +1,17 @@
|
||||
Title: Status
|
||||
Order: 6
|
||||
RedirectFrom: status
|
||||
---
|
||||
|
||||
Spectre.Console can display information about long running tasks in the console.
|
||||
|
||||
<img src="assets/images/status.gif" style="max-width: 100%;margin-bottom:20px;">
|
||||
<img src="../assets/images/status.gif" style="max-width: 100%;margin-bottom:20px;">
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle icon-web"></i> 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.
|
||||
</div>
|
||||
|
||||
If the current terminal isn't considered "interactive", such as when running
|
||||
in a continuous integration system, or the terminal can't display
|
@ -7,6 +7,11 @@ one or many items from a provided list.
|
||||
|
||||
<img src="../assets/images/multiselection.gif" style="width: 100%;" />
|
||||
|
||||
<div class="alert alert-warning" role="alert" style="margin-top:20px;">
|
||||
<i class="fas fa-exclamation-triangle icon-web"></i> The use of prompts
|
||||
insides status or progress displays is not supported.
|
||||
</div>
|
||||
|
||||
# Usage
|
||||
|
||||
```csharp
|
||||
|
@ -7,6 +7,11 @@ a single item from a provided list.
|
||||
|
||||
<img src="../assets/images/selection.gif" style="width: 100%;" />
|
||||
|
||||
<div class="alert alert-warning" role="alert" style="margin-top:20px;">
|
||||
<i class="fas fa-exclamation-triangle icon-web"></i> Using prompts inside
|
||||
status or progress displays, are not supported.
|
||||
</div>
|
||||
|
||||
# Usage
|
||||
|
||||
```csharp
|
||||
|
@ -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<TResult>`.
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle icon-web"></i> The use of prompts
|
||||
insides status or progress displays is not supported.
|
||||
</div>
|
||||
|
||||
# Confirmation
|
||||
|
||||
```csharp
|
||||
|
@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"cake.tool": {
|
||||
"version": "1.0.0-rc0002",
|
||||
"version": "1.1.0",
|
||||
"commands": [
|
||||
"dotnet-cake"
|
||||
]
|
||||
|
@ -59,6 +59,7 @@ namespace Cursor
|
||||
new MultiSelectionPrompt<string>()
|
||||
.PageSize(10)
|
||||
.Title("What are your [green]favorite fruits[/]?")
|
||||
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||
.InstructionsText("[grey](Press [blue]<space>[/] to toggle a fruit, [green]<enter>[/] to accept)[/]")
|
||||
.AddChoices(new[]
|
||||
{
|
||||
@ -75,6 +76,7 @@ namespace Cursor
|
||||
fruit = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.Title("Ok, but if you could only choose [green]one[/]?")
|
||||
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||
.AddChoices(favorites));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
18
src/Spectre.Console.Testing/Fakes/FakeExclusivityMode.cs
Normal file
18
src/Spectre.Console.Testing/Fakes/FakeExclusivityMode.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Console.Testing
|
||||
{
|
||||
public sealed class FakeExclusivityMode : IExclusivityMode
|
||||
{
|
||||
public T Run<T>(Func<T> func)
|
||||
{
|
||||
return func();
|
||||
}
|
||||
|
||||
public async Task<T> Run<T>(Func<Task<T>> func)
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -30,6 +30,11 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public InteractionSupport Interactive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exclusivity mode.
|
||||
/// </summary>
|
||||
public IExclusivityMode? ExclusivityMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile enrichments settings.
|
||||
/// </summary>
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||
/// </summary>
|
||||
public static partial class AnsiConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the specified function in exclusive mode.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result type.</typeparam>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <param name="func">The func to run in exclusive mode.</param>
|
||||
/// <returns>The result of the function.</returns>
|
||||
public static T RunExclusive<T>(this IAnsiConsole console, Func<T> func)
|
||||
{
|
||||
return console.ExclusivityMode.Run(func);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the specified function in exclusive mode asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result type.</typeparam>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <param name="func">The func to run in exclusive mode.</param>
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,11 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
IAnsiConsoleInput Input { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exclusivity mode.
|
||||
/// </summary>
|
||||
IExclusivityMode ExclusivityMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the render pipeline.
|
||||
/// </summary>
|
||||
|
27
src/Spectre.Console/IExclusivityMode.cs
Normal file
27
src/Spectre.Console/IExclusivityMode.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an exclusivity mode.
|
||||
/// </summary>
|
||||
public interface IExclusivityMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the specified function in exclusive mode.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
T Run<T>(Func<T> func);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the specified function in exclusive mode asynchronously.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
using Wcwidth;
|
||||
|
||||
|
57
src/Spectre.Console/Internal/DefaultExclusivityMode.cs
Normal file
57
src/Spectre.Console/Internal/DefaultExclusivityMode.cs
Normal file
@ -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<T>(Func<T> 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<T> Run<T>(Func<Task<T>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,9 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
public IAnsiConsoleInput Input => _console.Input;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RenderPipeline Pipeline => _console.Pipeline;
|
||||
|
||||
|
@ -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()
|
||||
|
@ -72,6 +72,11 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
public List<T> 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<T>(
|
||||
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<T>(
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -68,35 +68,38 @@ namespace Spectre.Console
|
||||
"terminal does not support ANSI escape sequences.");
|
||||
}
|
||||
|
||||
var converter = Converter ?? TypeConverterHelper.ConvertToString;
|
||||
var list = new RenderableSelectionList<T>(
|
||||
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<T>(
|
||||
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];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<T>(input, out result) || result == null)
|
||||
{
|
||||
console.MarkupLine(InvalidChoiceMessage);
|
||||
console.MarkupLine(ValidationErrorMessage);
|
||||
WritePrompt(console);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!TypeConverterHelper.TryConvertFromString<T>(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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
Loading…
x
Reference in New Issue
Block a user