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; } }