mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-29 00:19:26 +08:00 
			
		
		
		
	Add interactive prompts for selecting values
* Adds SelectionPrompt * Adds MultiSelectionPrompt Closes #210
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						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 | ||||
| Order: 4 | ||||
| Title: Text | ||||
| Order: 0 | ||||
| RedirectFrom: prompt | ||||
| --- | ||||
| 
 | ||||
| Sometimes you want to get some input from the user, and for this | ||||
| @@ -1,3 +1,4 @@ | ||||
| using System.Collections.Generic; | ||||
| using Spectre.Console; | ||||
|  | ||||
| namespace Cursor | ||||
| @@ -20,26 +21,87 @@ namespace Cursor | ||||
|                 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.Render(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned()); | ||||
|             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.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.Render(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned()); | ||||
|             var age = AnsiConsole.Prompt( | ||||
|  | ||||
|             return AnsiConsole.Prompt( | ||||
|                 new TextPrompt<int>("How [green]old[/] are you?") | ||||
|                     .PromptStyle("green") | ||||
|                     .ValidationErrorMessage("[red]That's not a valid age[/]") | ||||
| @@ -52,33 +114,27 @@ namespace Cursor | ||||
|                             _ => ValidationResult.Success(), | ||||
|                         }; | ||||
|                     })); | ||||
|         } | ||||
|  | ||||
|             // Secret | ||||
|         private static string AskPassword() | ||||
|         { | ||||
|             AnsiConsole.WriteLine(); | ||||
|             AnsiConsole.Render(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned()); | ||||
|             var password = AnsiConsole.Prompt( | ||||
|  | ||||
|             return AnsiConsole.Prompt( | ||||
|                 new TextPrompt<string>("Enter [green]password[/]?") | ||||
|                     .PromptStyle("red") | ||||
|                     .Secret()); | ||||
|         } | ||||
|  | ||||
|             // Optional | ||||
|         private static string AskColor() | ||||
|         { | ||||
|             AnsiConsole.WriteLine(); | ||||
|             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[/]?") | ||||
|                     .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()) | ||||
|                 { | ||||
|                     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()) | ||||
|                 { | ||||
|                     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 | ||||
|         { | ||||
|             get { return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth); } | ||||
|             get { return ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth); } | ||||
|         } | ||||
|  | ||||
|         public int Height | ||||
|         { | ||||
|             get { return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight); } | ||||
|             get { return ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalHeight); } | ||||
|         } | ||||
|  | ||||
|         public FallbackBackend(TextWriter @out, Capabilities capabilities) | ||||
|   | ||||
| @@ -4,7 +4,7 @@ namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class ConsoleHelper | ||||
|     { | ||||
|         public static int GetSafeBufferWidth(int defaultValue = Constants.DefaultBufferWidth) | ||||
|         public static int GetSafeWidth(int defaultValue = Constants.DefaultTerminalWidth) | ||||
|         { | ||||
|             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 | ||||
|             { | ||||
|                 var height = System.Console.BufferHeight; | ||||
|                 var height = System.Console.WindowHeight; | ||||
|                 if (height == 0) | ||||
|                 { | ||||
|                     height = defaultValue; | ||||
|   | ||||
| @@ -2,8 +2,8 @@ namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class Constants | ||||
|     { | ||||
|         public const int DefaultBufferWidth = 80; | ||||
|         public const int DefaultBufferHeight = 9001; | ||||
|         public const int DefaultTerminalWidth = 80; | ||||
|         public const int DefaultTerminalHeight = 24; | ||||
|  | ||||
|         public const string EmptyLink = "https://emptylink"; | ||||
|     } | ||||
|   | ||||
| @@ -9,6 +9,8 @@ namespace Spectre.Console.Rendering | ||||
|         private IRenderable? _renderable; | ||||
|         private SegmentShape? _shape; | ||||
|  | ||||
|         public bool HasRenderable => _renderable != null; | ||||
|  | ||||
|         public void SetRenderable(IRenderable renderable) | ||||
|         { | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user