Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/en/prompt-types/multi-select.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ var cities = Prompt.MultiSelect(new MultiSelectOptions<string>
| `TextSelector` | `Func<T, string>` | `x => x.ToString()!` | Display text selector |
| `LoopingSelection` | `bool` | `true` | Whether to loop at the beginning/end of the list |

## Selection Rules

- `Items` is read once, when the prompt starts. Equal items (compared with `EqualityComparer<T>.Default`) are listed once, at the position of the first one.
- `DefaultValues` that are not in `Items` are ignored.
- The result contains the selected items in the order of `Items`.
- `Minimum` and `Maximum` limit the number of selected items:

| Operation | Result |
|-----------|--------|
| <kbd>Space</kbd> on an unchecked item at `Maximum` | The item is not checked, and an error is shown. |
| <kbd>Ctrl</kbd>+<kbd>A</kbd> or <kbd>Ctrl</kbd>+<kbd>I</kbd> that would exceed `Maximum` | Nothing changes, and an error is shown. |
| More `DefaultValues` than `Maximum` | They are all checked. <kbd>Enter</kbd> is rejected until enough are unchecked. |
| <kbd>Enter</kbd> below `Minimum` or above `Maximum` | Rejected with an error. |

<kbd>Ctrl</kbd>+<kbd>A</kbd> and <kbd>Ctrl</kbd>+<kbd>I</kbd> only affect the items that match the current filter. Checked items hidden by the filter stay checked.

Before the prompt is shown, `ArgumentOutOfRangeException` is thrown when `Minimum` is negative, when `Maximum` is less than `Minimum`, or when `Minimum` is greater than the number of items, because the prompt could not be answered. For an empty `Items`, set `Minimum` to `0`.

## With Enum Type

```csharp
Expand Down
7 changes: 7 additions & 0 deletions docs/en/prompt-types/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ var city = Prompt.Select("Select your city",
Console.WriteLine($"Hello, {city}!");
```

## Item Rules

- `Items` is read once, when the prompt starts. Equal items (compared with `EqualityComparer<T>.Default`) are listed once, at the position of the first one.
- `DefaultValue` selects the matching item when the prompt starts. If it is not in `Items`, it does not select anything. A value of a compatible type, such as an `int` for a `long` prompt, is converted; an incompatible one throws `ArgumentException`.
- <kbd>Enter</kbd> with nothing selected shows an error. When only one item is listed, with or without a filter, or when the filter exactly matches an item's text, that item is selected.
- An empty `Items` throws `ArgumentException` before the prompt is shown, because nothing could be selected.

## With Enum Type

When using an enum type, items are automatically generated:
Expand Down
18 changes: 18 additions & 0 deletions docs/ja/prompt-types/multi-select.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ var cities = Prompt.MultiSelect(new MultiSelectOptions<string>
| `TextSelector` | `Func<T, string>` | `x => x.ToString()!` | 表示テキストセレクタ |
| `LoopingSelection` | `bool` | `true` | リストの先頭/末尾でループするかどうか |

## 選択のルール

- `Items` はプロンプトの開始時に 1 回だけ読み取ります。等しい項目(`EqualityComparer<T>.Default` で比較)は、最初の位置に 1 つだけ表示します。
- `Items` に含まれない `DefaultValues` は無視されます。
- 結果には、選択された項目が `Items` の順序で含まれます。
- `Minimum` と `Maximum` は選択数を制限します:

| 操作 | 結果 |
|------|------|
| 選択数が `Maximum` のときに、未選択の項目で <kbd>Space</kbd> | 項目は選択されず、エラーを表示します。 |
| `Maximum` を超える <kbd>Ctrl</kbd>+<kbd>A</kbd> または <kbd>Ctrl</kbd>+<kbd>I</kbd> | 何も変更せず、エラーを表示します。 |
| `Maximum` より多い `DefaultValues` | すべて選択された状態で始まります。十分に選択を外すまで <kbd>Enter</kbd> は拒否されます。 |
| `Minimum` 未満または `Maximum` 超過での <kbd>Enter</kbd> | エラーを表示して拒否します。 |

<kbd>Ctrl</kbd>+<kbd>A</kbd> と <kbd>Ctrl</kbd>+<kbd>I</kbd> は、現在のフィルターに一致する項目だけに作用します。フィルターで隠れている選択済みの項目は、選択されたままです。

`Minimum` が負の値、`Maximum` が `Minimum` より小さい、または `Minimum` が項目数より大きい場合は、プロンプトに回答できないため、表示する前に `ArgumentOutOfRangeException` をスローします。`Items` が空の場合は `Minimum` を `0` にしてください。

## Enum 型の使用

```csharp
Expand Down
7 changes: 7 additions & 0 deletions docs/ja/prompt-types/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ var city = Prompt.Select("Select your city",
Console.WriteLine($"Hello, {city}!");
```

## 項目のルール

- `Items` はプロンプトの開始時に 1 回だけ読み取ります。等しい項目(`EqualityComparer<T>.Default` で比較)は、最初の位置に 1 つだけ表示します。
- `DefaultValue` は、プロンプトの開始時に一致する項目を選択します。`Items` に含まれない場合、`DefaultValue` は何も選択しません。互換性のある型の値(`long` のプロンプトに対する `int` など)は変換され、互換性のない型は `ArgumentException` をスローします。
- 何も選択されていない状態で <kbd>Enter</kbd> を押すとエラーを表示します。表示される項目が 1 つだけの場合(フィルターの有無にかかわらず)や、フィルターが項目のテキストと完全に一致する場合は、その項目が選択されます。
- `Items` が空の場合は、何も選択できないため、プロンプトを表示する前に `ArgumentException` をスローします。

## Enum 型の使用

Enum 型を使用すると、項目が自動的に生成されます:
Expand Down
30 changes: 25 additions & 5 deletions src/Sharprompt/Forms/MultiSelectForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@ namespace Sharprompt.Forms;

internal class MultiSelectForm<T> : SelectFormBase<T, IEnumerable<T>> where T : notnull
{
public MultiSelectForm(MultiSelectOptions<T> options, PromptConfiguration configuration) : base(configuration)
public MultiSelectForm(MultiSelectOptions<T> options, PromptConfiguration configuration) : this(options, Prepare(options), configuration)
{
options.EnsureOptions();
}

private MultiSelectForm(MultiSelectOptions<T> options, T[] items, PromptConfiguration configuration) : base(configuration)
{
_options = options;

_items = options.Items.ToArray();
_items = items;

InitializePaginator(_items, options.PageSize, Optional<T>.Empty, options.TextSelector, options.LoopingSelection);

// Default values that are not in Items can never be returned, so ignore them
// instead of letting them count towards Minimum.
var items = new HashSet<T>(_items);
var itemSet = new HashSet<T>(_items);

foreach (var defaultValue in options.DefaultValues)
{
if (items.Contains(defaultValue))
if (itemSet.Contains(defaultValue))
{
_selectedItems.Add(defaultValue);
}
Expand All @@ -40,6 +42,24 @@ public MultiSelectForm(MultiSelectOptions<T> options, PromptConfiguration config
private readonly MultiSelectOptions<T> _options;
private readonly T[] _items;

// Runs before the base constructor creates the console driver, so invalid options throw without leaving it undisposed.
private static T[] Prepare(MultiSelectOptions<T> options)
{
options.EnsureOptions();

// Items are enumerated once, and rendering and the result both use this snapshot. Equal items are listed
// once, at the position of the first; otherwise checking one would check all of them and return each.
var items = options.Items.Distinct().ToArray();

// Enter would always be rejected, so the prompt could only be canceled.
if (options.Minimum > items.Length)
{
throw new ArgumentOutOfRangeException(nameof(options.Minimum), string.Format(Resource.Validation_Minimum_ExceedsItems, options.Minimum, items.Length));
}

return items;
}

private readonly HashSet<T> _selectedItems = [];

protected override void InputTemplate(OffscreenBuffer offscreenBuffer)
Expand Down
26 changes: 23 additions & 3 deletions src/Sharprompt/Forms/SelectForm.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

using Sharprompt.Internal;
using Sharprompt.Strings;
Expand All @@ -9,17 +10,36 @@ namespace Sharprompt.Forms;

internal class SelectForm<T> : SelectFormBase<T, T> where T : notnull
{
public SelectForm(SelectOptions<T> options, PromptConfiguration configuration) : base(configuration)
public SelectForm(SelectOptions<T> options, PromptConfiguration configuration) : this(options, Prepare(options), configuration)
{
options.EnsureOptions();
}

private SelectForm(SelectOptions<T> options, (T[] Items, Optional<T> DefaultValue) prepared, PromptConfiguration configuration) : base(configuration)
{
_options = options;

InitializePaginator(options.Items, options.PageSize, Optional<T>.Create(options.DefaultValue, nameof(options.DefaultValue)), options.TextSelector, options.LoopingSelection);
InitializePaginator(prepared.Items, options.PageSize, prepared.DefaultValue, options.TextSelector, options.LoopingSelection);
}

private readonly SelectOptions<T> _options;

// Runs before the base constructor creates the console driver, so invalid options throw without leaving it undisposed.
private static (T[] Items, Optional<T> DefaultValue) Prepare(SelectOptions<T> options)
{
options.EnsureOptions();

// Items are enumerated once; equal items are listed once, at the position of the first.
var items = options.Items.Distinct().ToArray();

// Nothing could be selected, so the prompt could only be canceled.
if (items.Length == 0)
{
throw new ArgumentException(Resource.Validation_Items_Empty, nameof(options.Items));
}

return (items, Optional<T>.Create(options.DefaultValue, nameof(options.DefaultValue)));
}

protected override void InputTemplate(OffscreenBuffer offscreenBuffer)
{
Paginator.UpdatePageSize(Math.Min(_options.PageSize, Height - 2));
Expand Down
18 changes: 18 additions & 0 deletions src/Sharprompt/Strings/Resource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Sharprompt/Strings/Resource.de-DE.resx
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,10 @@
<data name="ConfirmForm_Answer_No" xml:space="preserve">
<value>Nein</value>
</data>
<data name="Validation_Items_Empty" xml:space="preserve">
<value>Es muss mindestens ein Element zur Auswahl vorhanden sein</value>
</data>
<data name="Validation_Minimum_ExceedsItems" xml:space="preserve">
<value>Das Minimum ({0}) übersteigt die Anzahl der Elemente ({1})</value>
</data>
</root>
8 changes: 7 additions & 1 deletion src/Sharprompt/Strings/Resource.ja.resx
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,18 @@
<value>{0} 個以上の入力が必須です</value>
</data>
<data name="Validation_Minimum_OutOfRange" xml:space="preserve">
<value>最小値 ({1}) は不正です</value>
<value>最小値 ({0}) は不正です</value>
</data>
<data name="Validation_Minimum_SelectionRequired" xml:space="preserve">
<value>{0} 個以上の選択が必須です</value>
</data>
<data name="Validation_Type_NotParameterlessMethod" xml:space="preserve">
<value>引数を持つメソッドは指定できません</value>
</data>
<data name="Validation_Items_Empty" xml:space="preserve">
<value>選択できる項目が 1 つ以上必要です</value>
</data>
<data name="Validation_Minimum_ExceedsItems" xml:space="preserve">
<value>最小値 ({0}) が項目数 ({1}) を超えています</value>
</data>
</root>
6 changes: 6 additions & 0 deletions src/Sharprompt/Strings/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,10 @@
<data name="ConfirmForm_Answer_No" xml:space="preserve">
<value>No</value>
</data>
<data name="Validation_Items_Empty" xml:space="preserve">
<value>There must be at least one item to select</value>
</data>
<data name="Validation_Minimum_ExceedsItems" xml:space="preserve">
<value>The minimum ({0}) exceeds the number of items ({1})</value>
</data>
</root>
6 changes: 6 additions & 0 deletions src/Sharprompt/Strings/Resource.zh-Hans.resx
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,10 @@
<data name="ConfirmForm_Answer_No" xml:space="preserve">
<value>No</value>
</data>
<data name="Validation_Items_Empty" xml:space="preserve">
<value>至少需要一个可选择的项目</value>
</data>
<data name="Validation_Minimum_ExceedsItems" xml:space="preserve">
<value>最小值 ({0}) 超过了项目数 ({1})</value>
</data>
</root>
Loading
Loading