namespace Spectre.Console;
///
/// Represents a prompt.
///
/// The prompt result type.
public sealed class TextPrompt : IPrompt, IHasCulture
{
private readonly string _prompt;
private readonly StringComparer? _comparer;
///
/// Gets or sets the prompt style.
///
public Style? PromptStyle { get; set; }
///
/// Gets the list of choices.
///
public List Choices { get; } = new List();
///
/// Gets or sets the culture to use when converting input to object.
///
public CultureInfo? Culture { get; set; }
///
/// Gets or sets the message for invalid choices.
///
public string InvalidChoiceMessage { get; set; } = "[red]Please select one of the available options[/]";
///
/// Gets or sets a value indicating whether input should
/// be hidden in the console.
///
public bool IsSecret { get; set; }
///
/// Gets or sets the character to use while masking
/// a secret prompt.
///
public char? Mask { get; set; } = '*';
///
/// Gets or sets the validation error message.
///
public string ValidationErrorMessage { get; set; } = "[red]Invalid input[/]";
///
/// Gets or sets a value indicating whether or not
/// choices should be shown.
///
public bool ShowChoices { get; set; } = true;
///
/// Gets or sets a value indicating whether or not
/// default values should be shown.
///
public bool ShowDefaultValue { get; set; } = true;
///
/// Gets or sets a value indicating whether or not an empty result is valid.
///
public bool AllowEmpty { get; set; }
///
/// Gets or sets the converter to get the display string for a choice. By default
/// the corresponding is used.
///
public Func? Converter { get; set; } = TypeConverterHelper.ConvertToString;
///
/// Gets or sets the validator.
///
public Func? Validator { get; set; }
///
/// Gets or sets the style in which the default value is displayed.
///
public Style? DefaultValueStyle { get; set; }
///
/// Gets or sets the style in which the list of choices is displayed.
///
public Style? ChoicesStyle { get; set; }
///
/// Gets or sets the default value.
///
internal DefaultPromptValue? DefaultValue { get; set; }
///
/// Initializes a new instance of the class.
///
/// The prompt markup text.
/// The comparer used for choices.
public TextPrompt(string prompt, StringComparer? comparer = null)
{
_prompt = prompt ?? throw new System.ArgumentNullException(nameof(prompt));
_comparer = comparer;
}
///
/// Shows the prompt and requests input from the user.
///
/// The console to show the prompt in.
/// 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)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
return await console.RunExclusive(async () =>
{
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)
{
var input = await console.ReadLine(promptStyle, IsSecret, Mask, choices, cancellationToken).ConfigureAwait(false);
// Nothing entered?
if (string.IsNullOrWhiteSpace(input))
{
if (DefaultValue != null)
{
var defaultValue = converter(DefaultValue.Value);
console.Write(IsSecret ? defaultValue.Mask(Mask) : defaultValue, promptStyle);
console.WriteLine();
return DefaultValue.Value;
}
if (!AllowEmpty)
{
continue;
}
}
console.WriteLine();
T? result;
if (Choices.Count > 0)
{
if (choiceMap.TryGetValue(input, out result) && result != null)
{
return result;
}
else
{
console.MarkupLine(InvalidChoiceMessage);
WritePrompt(console);
continue;
}
}
else if (!TypeConverterHelper.TryConvertFromStringWithCulture(input, Culture, 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;
}
return result;
}
}).ConfigureAwait(false);
}
///
/// Writes the prompt to the console.
///
/// The console to write the prompt to.
private void WritePrompt(IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var builder = new StringBuilder();
builder.Append(_prompt.TrimEnd());
var appendSuffix = false;
if (ShowChoices && Choices.Count > 0)
{
appendSuffix = true;
var converter = Converter ?? TypeConverterHelper.ConvertToString;
var choices = string.Join("/", Choices.Select(choice => converter(choice)));
var choicesStyle = ChoicesStyle?.ToMarkup() ?? "blue";
builder.AppendFormat(CultureInfo.InvariantCulture, " [{0}][[{1}]][/]", choicesStyle, choices);
}
if (ShowDefaultValue && DefaultValue != null)
{
appendSuffix = true;
var converter = Converter ?? TypeConverterHelper.ConvertToString;
var defaultValueStyle = DefaultValueStyle?.ToMarkup() ?? "green";
var defaultValue = converter(DefaultValue.Value);
builder.AppendFormat(
CultureInfo.InvariantCulture,
" [{0}]({1})[/]",
defaultValueStyle,
IsSecret ? defaultValue.Mask(Mask) : defaultValue);
}
var markup = builder.ToString().Trim();
if (appendSuffix)
{
markup += ":";
}
console.Markup(markup + " ");
}
private bool ValidateResult(T value, [NotNullWhen(false)] out string? message)
{
if (Validator != null)
{
var result = Validator(value);
if (!result.Successful)
{
message = result.Message ?? ValidationErrorMessage;
return false;
}
}
message = null;
return true;
}
}