mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
Add interactive prompts for selecting values
* Adds SelectionPrompt * Adds MultiSelectionPrompt Closes #210
This commit is contained in:
parent
3a593857c8
commit
0e0f4b4220
BIN
docs/input/assets/images/multiselection.gif
Normal file
BIN
docs/input/assets/images/multiselection.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 229 KiB |
BIN
docs/input/assets/images/selection.gif
Normal file
BIN
docs/input/assets/images/selection.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 208 KiB |
12
docs/input/prompts/index.cshtml
Normal file
12
docs/input/prompts/index.cshtml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Title: Prompts
|
||||||
|
Order: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
<h1>Sections</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
@foreach (IDocument child in OutputPages.GetChildrenOf(Document))
|
||||||
|
{
|
||||||
|
<li>@Html.DocumentLink(child)</li>
|
||||||
|
}
|
||||||
|
</ul>
|
31
docs/input/prompts/multiselection.md
Normal file
31
docs/input/prompts/multiselection.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Title: Multi Selection
|
||||||
|
Order: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
The `MultiSelectionPrompt` can be used when you want the user to select
|
||||||
|
one or many items from a provided list.
|
||||||
|
|
||||||
|
<img src="../assets/images/multiselection.gif" style="width: 100%;" />
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Ask for the user's favorite fruits
|
||||||
|
var fruits = AnsiConsole.Prompt(
|
||||||
|
new MultiSelectionPrompt<string>()
|
||||||
|
.Title("What are your [green]favorite fruits[/]?")
|
||||||
|
.NotRequired() // Not required to have a favorite fruit
|
||||||
|
.PageSize(10)
|
||||||
|
.AddChoice("Apple")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Cocunut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Write the selected fruits to the terminal
|
||||||
|
foreach (string fruit in fruits)
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine(fruit);
|
||||||
|
}
|
||||||
|
```
|
27
docs/input/prompts/selection.md
Normal file
27
docs/input/prompts/selection.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Title: Selection
|
||||||
|
Order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
The `SelectionPrompt` can be used when you want the user to select
|
||||||
|
a single item from a provided list.
|
||||||
|
|
||||||
|
<img src="../assets/images/selection.gif" style="width: 100%;" />
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Ask for the user's favorite fruit
|
||||||
|
var fruit = AnsiConsole.Prompt(
|
||||||
|
new SelectionPrompt<string>()
|
||||||
|
.Title("What's your [green]favorite fruit[/]?")
|
||||||
|
.PageSize(10)
|
||||||
|
.AddChoice("Apple")
|
||||||
|
.AddChoices(new[] {
|
||||||
|
"Apricot", "Avocado",
|
||||||
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Cocunut",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Echo the fruit back to the terminal
|
||||||
|
AnsiConsole.WriteLine($"I agree. {fruit} is tasty!");
|
||||||
|
```
|
@ -1,5 +1,6 @@
|
|||||||
Title: Prompt
|
Title: Text
|
||||||
Order: 4
|
Order: 0
|
||||||
|
RedirectFrom: prompt
|
||||||
---
|
---
|
||||||
|
|
||||||
Sometimes you want to get some input from the user, and for this
|
Sometimes you want to get some input from the user, and for this
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace Cursor
|
namespace Cursor
|
||||||
@ -20,26 +21,87 @@ namespace Cursor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String
|
// Ask the user for some different things
|
||||||
|
var name = AskName();
|
||||||
|
var fruit = AskFruit();
|
||||||
|
var sport = AskSport();
|
||||||
|
var age = AskAge();
|
||||||
|
var password = AskPassword();
|
||||||
|
var color = AskColor();
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
AnsiConsole.Render(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
|
||||||
|
.RoundedBorder()
|
||||||
|
.BorderColor(Color.Grey)
|
||||||
|
.AddRow("[grey]Name[/]", name)
|
||||||
|
.AddRow("[grey]Favorite fruit[/]", fruit)
|
||||||
|
.AddRow("[grey]Favorite sport[/]", sport)
|
||||||
|
.AddRow("[grey]Age[/]", age.ToString())
|
||||||
|
.AddRow("[grey]Password[/]", password)
|
||||||
|
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string AskName()
|
||||||
|
{
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Render(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
|
||||||
var name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
|
var name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
// String with choices
|
private static string AskFruit()
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Render(new Rule("[yellow]Lists[/]").RuleStyle("grey").LeftAligned());
|
||||||
|
|
||||||
|
var favorites = AnsiConsole.Prompt(
|
||||||
|
new MultiSelectionPrompt<string>()
|
||||||
|
.PageSize(10)
|
||||||
|
.Title("What are your [green]favorite fruits[/]?")
|
||||||
|
.AddChoices(new[]
|
||||||
|
{
|
||||||
|
"Apple", "Apricot", "Avocado", "Banana", "Blackcurrant", "Blueberry",
|
||||||
|
"Cherry", "Cloudberry", "Cocunut", "Date", "Dragonfruit", "Durian",
|
||||||
|
"Egg plant", "Elderberry", "Fig", "Grape", "Guava", "Honeyberry",
|
||||||
|
"Jackfruit", "Jambul", "Kiwano", "Kiwifruit", "Lime", "Lylo",
|
||||||
|
"Lychee", "Melon", "Mulberry", "Nectarine", "Orange", "Olive"
|
||||||
|
}));
|
||||||
|
|
||||||
|
var fruit = favorites.Count == 1 ? favorites[0] : null;
|
||||||
|
if (string.IsNullOrWhiteSpace(fruit))
|
||||||
|
{
|
||||||
|
fruit = AnsiConsole.Prompt(
|
||||||
|
new SelectionPrompt<string>()
|
||||||
|
.Title("Ok, but if you could only choose [green]one[/]?")
|
||||||
|
.AddChoices(favorites));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("Your selected: [yellow]{0}[/]", fruit);
|
||||||
|
return fruit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string AskSport()
|
||||||
|
{
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Render(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
|
||||||
var fruit = AnsiConsole.Prompt(
|
|
||||||
new TextPrompt<string>("What's your [green]favorite fruit[/]?")
|
|
||||||
.InvalidChoiceMessage("[red]That's not a valid fruit[/]")
|
|
||||||
.DefaultValue("Orange")
|
|
||||||
.AddChoice("Apple")
|
|
||||||
.AddChoice("Banana")
|
|
||||||
.AddChoice("Orange"));
|
|
||||||
|
|
||||||
// Integer
|
return AnsiConsole.Prompt(
|
||||||
|
new TextPrompt<string>("What's your [green]favorite sport[/]?")
|
||||||
|
.InvalidChoiceMessage("[red]That's not a valid fruit[/]")
|
||||||
|
.DefaultValue("Lol")
|
||||||
|
.AddChoice("Soccer")
|
||||||
|
.AddChoice("Hockey")
|
||||||
|
.AddChoice("Basketball"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AskAge()
|
||||||
|
{
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Render(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
|
||||||
var age = AnsiConsole.Prompt(
|
|
||||||
|
return AnsiConsole.Prompt(
|
||||||
new TextPrompt<int>("How [green]old[/] are you?")
|
new TextPrompt<int>("How [green]old[/] are you?")
|
||||||
.PromptStyle("green")
|
.PromptStyle("green")
|
||||||
.ValidationErrorMessage("[red]That's not a valid age[/]")
|
.ValidationErrorMessage("[red]That's not a valid age[/]")
|
||||||
@ -52,33 +114,27 @@ namespace Cursor
|
|||||||
_ => ValidationResult.Success(),
|
_ => ValidationResult.Success(),
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// Secret
|
private static string AskPassword()
|
||||||
|
{
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Render(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
|
||||||
var password = AnsiConsole.Prompt(
|
|
||||||
|
return AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>("Enter [green]password[/]?")
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
.PromptStyle("red")
|
.PromptStyle("red")
|
||||||
.Secret());
|
.Secret());
|
||||||
|
}
|
||||||
|
|
||||||
// Optional
|
private static string AskColor()
|
||||||
|
{
|
||||||
AnsiConsole.WriteLine();
|
AnsiConsole.WriteLine();
|
||||||
AnsiConsole.Render(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Render(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
|
||||||
var color = AnsiConsole.Prompt(
|
|
||||||
|
return AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
|
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
|
||||||
.AllowEmpty());
|
.AllowEmpty());
|
||||||
|
|
||||||
// Summary
|
|
||||||
AnsiConsole.WriteLine();
|
|
||||||
AnsiConsole.Render(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
|
|
||||||
AnsiConsole.Render(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
|
|
||||||
.RoundedBorder()
|
|
||||||
.BorderColor(Color.Grey)
|
|
||||||
.AddRow("[grey]Name[/]", name)
|
|
||||||
.AddRow("[grey]Favorite fruit[/]", fruit)
|
|
||||||
.AddRow("[grey]Age[/]", age.ToString())
|
|
||||||
.AddRow("[grey]Password[/]", password)
|
|
||||||
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/Spectre.Console/Extensions/Int32Extensions.cs
Normal file
20
src/Spectre.Console/Extensions/Int32Extensions.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
internal static class Int32Extensions
|
||||||
|
{
|
||||||
|
public static int Clamp(this int value, int min, int max)
|
||||||
|
{
|
||||||
|
if (value <= min)
|
||||||
|
{
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value >= max)
|
||||||
|
{
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,10 +26,10 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
if (_out.IsStandardOut())
|
if (_out.IsStandardOut())
|
||||||
{
|
{
|
||||||
return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
|
return ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Constants.DefaultBufferWidth;
|
return Constants.DefaultTerminalWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,10 +39,10 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
if (_out.IsStandardOut())
|
if (_out.IsStandardOut())
|
||||||
{
|
{
|
||||||
return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
|
return ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Constants.DefaultBufferHeight;
|
return Constants.DefaultTerminalHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ namespace Spectre.Console.Internal
|
|||||||
|
|
||||||
public int Width
|
public int Width
|
||||||
{
|
{
|
||||||
get { return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth); }
|
get { return ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Height
|
public int Height
|
||||||
{
|
{
|
||||||
get { return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight); }
|
get { return ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalHeight); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public FallbackBackend(TextWriter @out, Capabilities capabilities)
|
public FallbackBackend(TextWriter @out, Capabilities capabilities)
|
||||||
|
@ -4,7 +4,7 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
internal static class ConsoleHelper
|
internal static class ConsoleHelper
|
||||||
{
|
{
|
||||||
public static int GetSafeBufferWidth(int defaultValue = Constants.DefaultBufferWidth)
|
public static int GetSafeWidth(int defaultValue = Constants.DefaultTerminalWidth)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -22,11 +22,11 @@ namespace Spectre.Console.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetSafeBufferHeight(int defaultValue = Constants.DefaultBufferWidth)
|
public static int GetSafeHeight(int defaultValue = Constants.DefaultTerminalHeight)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var height = System.Console.BufferHeight;
|
var height = System.Console.WindowHeight;
|
||||||
if (height == 0)
|
if (height == 0)
|
||||||
{
|
{
|
||||||
height = defaultValue;
|
height = defaultValue;
|
||||||
|
@ -2,8 +2,8 @@ namespace Spectre.Console.Internal
|
|||||||
{
|
{
|
||||||
internal static class Constants
|
internal static class Constants
|
||||||
{
|
{
|
||||||
public const int DefaultBufferWidth = 80;
|
public const int DefaultTerminalWidth = 80;
|
||||||
public const int DefaultBufferHeight = 9001;
|
public const int DefaultTerminalHeight = 24;
|
||||||
|
|
||||||
public const string EmptyLink = "https://emptylink";
|
public const string EmptyLink = "https://emptylink";
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ namespace Spectre.Console.Rendering
|
|||||||
private IRenderable? _renderable;
|
private IRenderable? _renderable;
|
||||||
private SegmentShape? _shape;
|
private SegmentShape? _shape;
|
||||||
|
|
||||||
|
public bool HasRenderable => _renderable != null;
|
||||||
|
|
||||||
public void SetRenderable(IRenderable renderable)
|
public void SetRenderable(IRenderable renderable)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
112
src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs
Normal file
112
src/Spectre.Console/Widgets/Prompt/MultiSelectionPrompt.cs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a list prompt.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title.
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the choices.
|
||||||
|
/// </summary>
|
||||||
|
public List<T> Choices { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the converter to get the display string for a choice. By default
|
||||||
|
/// the corresponding <see cref="TypeConverter"/> is used.
|
||||||
|
/// </summary>
|
||||||
|
public Func<T, string>? Converter { get; set; } = TypeConverterHelper.ConvertToString;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the page size.
|
||||||
|
/// Defaults to <c>10</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not
|
||||||
|
/// at least one selection is required.
|
||||||
|
/// </summary>
|
||||||
|
public bool Required { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MultiSelectionPrompt{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public MultiSelectionPrompt()
|
||||||
|
{
|
||||||
|
Choices = new List<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public List<T> Show(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
if (!console.Capabilities.SupportsInteraction)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Cannot show multi selection prompt since the current " +
|
||||||
|
"terminal isn't interactive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!console.Capabilities.SupportsAnsi)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Cannot show multi selection prompt since the current " +
|
||||||
|
"terminal does not support ANSI escape sequences.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var converter = Converter ?? TypeConverterHelper.ConvertToString;
|
||||||
|
|
||||||
|
var list = new RenderableMultiSelectionList<T>(console, Title, PageSize, Choices, converter);
|
||||||
|
using (new RenderHookScope(console, list))
|
||||||
|
{
|
||||||
|
console.Cursor.Hide();
|
||||||
|
list.Redraw();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Update(key.Key))
|
||||||
|
{
|
||||||
|
list.Redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
console.Cursor.Show();
|
||||||
|
|
||||||
|
return list.Selections
|
||||||
|
.Select(index => Choices[index])
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="MultiSelectionPrompt{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class MultiSelectionPromptExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choice">The choice to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> AddChoice<T>(this MultiSelectionPrompt<T> obj, T choice)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.Add(choice);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choices">The choices to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> AddChoices<T>(this MultiSelectionPrompt<T> obj, params T[] choices)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.AddRange(choices);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choices">The choices to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> AddChoices<T>(this MultiSelectionPrompt<T> obj, IEnumerable<T> choices)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.AddRange(choices);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the title.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="title">The title markup text.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> Title<T>(this MultiSelectionPrompt<T> obj, string? title)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Title = title;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how many choices that are displayed to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="pageSize">The number of choices that are displayed to the user.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> PageSize<T>(this MultiSelectionPrompt<T> obj, int pageSize)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSize <= 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Page size must be greater or equal to 3.", nameof(pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.PageSize = pageSize;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requires no choice to be selected.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> NotRequired<T>(this MultiSelectionPrompt<T> obj)
|
||||||
|
{
|
||||||
|
return Required(obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requires a choice to be selected.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> Required<T>(this MultiSelectionPrompt<T> obj)
|
||||||
|
{
|
||||||
|
return Required(obj, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a value indicating whether or not at least one choice must be selected.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="required">Whether or not at least one choice must be selected.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> Required<T>(this MultiSelectionPrompt<T> obj, bool required)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Required = required;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the function to create a display string for a given choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="displaySelector">The function to get a display string for a given choice.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static MultiSelectionPrompt<T> UseConverter<T>(this MultiSelectionPrompt<T> obj, Func<T, string>? displaySelector)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Converter = displaySelector;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs
Normal file
124
src/Spectre.Console/Widgets/Prompt/Rendering/RenderableList.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
internal abstract class RenderableList<T> : IRenderHook
|
||||||
|
{
|
||||||
|
private readonly LiveRenderable _live;
|
||||||
|
private readonly object _lock;
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly int _requestedPageSize;
|
||||||
|
private readonly List<T> _choices;
|
||||||
|
private readonly Func<T, string> _converter;
|
||||||
|
private int _index;
|
||||||
|
|
||||||
|
public int Index => _index;
|
||||||
|
|
||||||
|
public RenderableList(IAnsiConsole console, int requestedPageSize, List<T> choices, Func<T, string>? converter)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
_requestedPageSize = requestedPageSize;
|
||||||
|
_choices = choices;
|
||||||
|
_converter = converter ?? throw new ArgumentNullException(nameof(converter));
|
||||||
|
_live = new LiveRenderable();
|
||||||
|
_lock = new object();
|
||||||
|
_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract int CalculatePageSize(int requestedPageSize);
|
||||||
|
protected abstract IRenderable Build(int pointerIndex, bool scrollable, IEnumerable<(int Original, int Index, string Item)> choices);
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_console.Render(_live.RestoreCursor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Redraw()
|
||||||
|
{
|
||||||
|
_console.Render(new ControlSequence(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Update(ConsoleKey key)
|
||||||
|
{
|
||||||
|
var index = key switch
|
||||||
|
{
|
||||||
|
ConsoleKey.UpArrow => _index - 1,
|
||||||
|
ConsoleKey.DownArrow => _index + 1,
|
||||||
|
_ => _index,
|
||||||
|
};
|
||||||
|
|
||||||
|
index = index.Clamp(0, _choices.Count - 1);
|
||||||
|
if (index != _index)
|
||||||
|
{
|
||||||
|
_index = index;
|
||||||
|
Build();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (!_live.HasRenderable)
|
||||||
|
{
|
||||||
|
Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return _live.PositionCursor();
|
||||||
|
|
||||||
|
foreach (var renderable in renderables)
|
||||||
|
{
|
||||||
|
yield return renderable;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return _live;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Build()
|
||||||
|
{
|
||||||
|
var pageSize = CalculatePageSize(_requestedPageSize);
|
||||||
|
var middleOfList = pageSize / 2;
|
||||||
|
|
||||||
|
var skip = 0;
|
||||||
|
var take = _choices.Count;
|
||||||
|
var pointer = _index;
|
||||||
|
|
||||||
|
var scrollable = _choices.Count > pageSize;
|
||||||
|
if (scrollable)
|
||||||
|
{
|
||||||
|
skip = Math.Max(0, _index - middleOfList);
|
||||||
|
take = Math.Min(pageSize, _choices.Count - skip);
|
||||||
|
|
||||||
|
if (_choices.Count - _index < middleOfList)
|
||||||
|
{
|
||||||
|
// Pointer should be below the end of the list
|
||||||
|
var diff = middleOfList - (_choices.Count - _index);
|
||||||
|
skip -= diff;
|
||||||
|
take += diff;
|
||||||
|
pointer = middleOfList + diff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Take skip into account
|
||||||
|
pointer -= skip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the list
|
||||||
|
_live.SetRenderable(Build(
|
||||||
|
pointer,
|
||||||
|
scrollable,
|
||||||
|
_choices.Skip(skip).Take(take)
|
||||||
|
.Enumerate()
|
||||||
|
.Select(x => (skip + x.Index, x.Index, _converter(x.Item)))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
internal sealed class RenderableMultiSelectionList<T> : RenderableList<T>
|
||||||
|
{
|
||||||
|
private const string Checkbox = "[[ ]]";
|
||||||
|
private const string SelectedCheckbox = "[[X]]";
|
||||||
|
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly string? _title;
|
||||||
|
private readonly Style _highlightStyle;
|
||||||
|
|
||||||
|
public HashSet<int> Selections { get; set; }
|
||||||
|
|
||||||
|
public RenderableMultiSelectionList(
|
||||||
|
IAnsiConsole console, string? title, int pageSize,
|
||||||
|
List<T> choices, Func<T, string>? converter)
|
||||||
|
: base(console, pageSize, choices, converter)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
_title = title;
|
||||||
|
_highlightStyle = new Style(foreground: Color.Blue);
|
||||||
|
|
||||||
|
Selections = new HashSet<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Select()
|
||||||
|
{
|
||||||
|
if (Selections.Contains(Index))
|
||||||
|
{
|
||||||
|
Selections.Remove(Index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Selections.Add(Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int CalculatePageSize(int requestedPageSize)
|
||||||
|
{
|
||||||
|
var pageSize = requestedPageSize;
|
||||||
|
if (pageSize > _console.Height - 5)
|
||||||
|
{
|
||||||
|
pageSize = _console.Height - 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IRenderable Build(int pointerIndex, bool scrollable, IEnumerable<(int Original, int Index, string Item)> choices)
|
||||||
|
{
|
||||||
|
var list = new List<IRenderable>();
|
||||||
|
|
||||||
|
if (_title != null)
|
||||||
|
{
|
||||||
|
list.Add(new Markup(_title));
|
||||||
|
}
|
||||||
|
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn().Padding(0, 0, 1, 0).NoWrap());
|
||||||
|
grid.AddColumn(new GridColumn().Padding(0, 0, 0, 0));
|
||||||
|
|
||||||
|
if (_title != null)
|
||||||
|
{
|
||||||
|
grid.AddEmptyRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var choice in choices)
|
||||||
|
{
|
||||||
|
var current = choice.Index == pointerIndex;
|
||||||
|
var selected = Selections.Contains(choice.Original);
|
||||||
|
|
||||||
|
var prompt = choice.Index == pointerIndex ? "> " : " ";
|
||||||
|
var checkbox = selected ? SelectedCheckbox : Checkbox;
|
||||||
|
|
||||||
|
var style = current ? _highlightStyle : Style.Plain;
|
||||||
|
|
||||||
|
grid.AddRow(
|
||||||
|
new Markup($"{prompt}{checkbox}", style),
|
||||||
|
new Markup(choice.Item.EscapeMarkup(), style));
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(grid);
|
||||||
|
list.Add(Text.Empty);
|
||||||
|
|
||||||
|
if (scrollable)
|
||||||
|
{
|
||||||
|
list.Add(new Markup("[grey](Move up and down to reveal more choices)[/]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(new Markup("[grey](Press <space> to select)[/]"));
|
||||||
|
|
||||||
|
return new Rows(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
internal sealed class RenderableSelectionList<T> : RenderableList<T>
|
||||||
|
{
|
||||||
|
private const string Prompt = ">";
|
||||||
|
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly string? _title;
|
||||||
|
private readonly Style _highlightStyle;
|
||||||
|
|
||||||
|
public RenderableSelectionList(IAnsiConsole console, string? title, int requestedPageSize, List<T> choices, Func<T, string>? converter)
|
||||||
|
: base(console, requestedPageSize, choices, converter)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
_title = title;
|
||||||
|
_highlightStyle = new Style(foreground: Color.Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int CalculatePageSize(int requestedPageSize)
|
||||||
|
{
|
||||||
|
var pageSize = requestedPageSize;
|
||||||
|
if (pageSize > _console.Height - 4)
|
||||||
|
{
|
||||||
|
pageSize = _console.Height - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IRenderable Build(int pointerIndex, bool scrollable, IEnumerable<(int Original, int Index, string Item)> choices)
|
||||||
|
{
|
||||||
|
var list = new List<IRenderable>();
|
||||||
|
|
||||||
|
if (_title != null)
|
||||||
|
{
|
||||||
|
list.Add(new Markup(_title));
|
||||||
|
}
|
||||||
|
|
||||||
|
var grid = new Grid();
|
||||||
|
grid.AddColumn(new GridColumn().Padding(0, 0, 1, 0).NoWrap());
|
||||||
|
grid.AddColumn(new GridColumn().Padding(0, 0, 0, 0));
|
||||||
|
|
||||||
|
if (_title != null)
|
||||||
|
{
|
||||||
|
grid.AddEmptyRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var choice in choices)
|
||||||
|
{
|
||||||
|
var current = choice.Index == pointerIndex;
|
||||||
|
|
||||||
|
var prompt = choice.Index == pointerIndex ? Prompt : string.Empty;
|
||||||
|
var style = current ? _highlightStyle : Style.Plain;
|
||||||
|
|
||||||
|
grid.AddRow(
|
||||||
|
new Markup(prompt, style),
|
||||||
|
new Markup(choice.Item.EscapeMarkup(), style));
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(grid);
|
||||||
|
|
||||||
|
if (scrollable)
|
||||||
|
{
|
||||||
|
list.Add(Text.Empty);
|
||||||
|
list.Add(new Markup("[grey](Move up and down to reveal more choices)[/]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Rows(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs
Normal file
91
src/Spectre.Console/Widgets/Prompt/SelectionPrompt.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a list prompt.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
public sealed class SelectionPrompt<T> : IPrompt<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the title.
|
||||||
|
/// </summary>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the choices.
|
||||||
|
/// </summary>
|
||||||
|
public List<T> Choices { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the converter to get the display string for a choice. By default
|
||||||
|
/// the corresponding <see cref="TypeConverter"/> is used.
|
||||||
|
/// </summary>
|
||||||
|
public Func<T, string>? Converter { get; set; } = TypeConverterHelper.ConvertToString;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the page size.
|
||||||
|
/// Defaults to <c>10</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SelectionPrompt{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public SelectionPrompt()
|
||||||
|
{
|
||||||
|
Choices = new List<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
T IPrompt<T>.Show(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
if (!console.Capabilities.SupportsInteraction)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Cannot show selection prompt since the current " +
|
||||||
|
"terminal isn't interactive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!console.Capabilities.SupportsAnsi)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Cannot show selection prompt since the current " +
|
||||||
|
"terminal does not support ANSI escape sequences.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var converter = Converter ?? TypeConverterHelper.ConvertToString;
|
||||||
|
|
||||||
|
var list = new RenderableSelectionList<T>(console, Title, PageSize, Choices, converter);
|
||||||
|
using (new RenderHookScope(console, list))
|
||||||
|
{
|
||||||
|
console.Cursor.Hide();
|
||||||
|
list.Redraw();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
return Choices[list.Index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
src/Spectre.Console/Widgets/Prompt/SelectionPromptExtensions.cs
Normal file
124
src/Spectre.Console/Widgets/Prompt/SelectionPromptExtensions.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="SelectionPrompt{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class SelectionPromptExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choice">The choice to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> AddChoice<T>(this SelectionPrompt<T> obj, T choice)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.Add(choice);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choices">The choices to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> AddChoices<T>(this SelectionPrompt<T> obj, params T[] choices)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.AddRange(choices);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="choices">The choices to add.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> AddChoices<T>(this SelectionPrompt<T> obj, IEnumerable<T> choices)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Choices.AddRange(choices);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the title.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="title">The title markup text.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> Title<T>(this SelectionPrompt<T> obj, string? title)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Title = title;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets how many choices that are displayed to the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="pageSize">The number of choices that are displayed to the user.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> PageSize<T>(this SelectionPrompt<T> obj, int pageSize)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSize <= 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Page size must be greater or equal to 3.", nameof(pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.PageSize = pageSize;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the function to create a display string for a given choice.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The prompt type.</typeparam>
|
||||||
|
/// <param name="obj">The prompt.</param>
|
||||||
|
/// <param name="displaySelector">The function to get a display string for a given choice.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static SelectionPrompt<T> UseConverter<T>(this SelectionPrompt<T> obj, Func<T, string>? displaySelector)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Converter = displaySelector;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user