mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-15 00:12:50 +08:00
Allow selections to wrap around
This commit is contained in:
parent
eb02c3d534
commit
4f0ec87522
@ -15,7 +15,8 @@ internal sealed class ListPrompt<T>
|
||||
public async Task<ListPromptState<T>> Show(
|
||||
ListPromptTree<T> tree,
|
||||
CancellationToken cancellationToken,
|
||||
int requestedPageSize = 15)
|
||||
int requestedPageSize = 15,
|
||||
bool wrapAround = false)
|
||||
{
|
||||
if (tree is null)
|
||||
{
|
||||
@ -37,7 +38,7 @@ internal sealed class ListPrompt<T>
|
||||
}
|
||||
|
||||
var nodes = tree.Traverse().ToList();
|
||||
var state = new ListPromptState<T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize));
|
||||
var state = new ListPromptState<T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize), wrapAround);
|
||||
var hook = new ListPromptRenderHook<T>(_console, () => BuildRenderable(state));
|
||||
|
||||
using (new RenderHookScope(_console, hook))
|
||||
|
@ -6,15 +6,17 @@ internal sealed class ListPromptState<T>
|
||||
public int Index { get; private set; }
|
||||
public int ItemCount => Items.Count;
|
||||
public int PageSize { get; }
|
||||
public bool WrapAround { get; }
|
||||
public IReadOnlyList<ListPromptItem<T>> Items { get; }
|
||||
|
||||
public ListPromptItem<T> Current => Items[Index];
|
||||
|
||||
public ListPromptState(IReadOnlyList<ListPromptItem<T>> items, int pageSize)
|
||||
public ListPromptState(IReadOnlyList<ListPromptItem<T>> items, int pageSize, bool wrapAround)
|
||||
{
|
||||
Index = 0;
|
||||
Items = items;
|
||||
PageSize = pageSize;
|
||||
WrapAround = wrapAround;
|
||||
}
|
||||
|
||||
public bool Update(ConsoleKey key)
|
||||
@ -30,7 +32,9 @@ internal sealed class ListPromptState<T>
|
||||
_ => Index,
|
||||
};
|
||||
|
||||
index = index.Clamp(0, ItemCount - 1);
|
||||
index = WrapAround
|
||||
? (ItemCount + (index % ItemCount)) % ItemCount
|
||||
: index.Clamp(0, ItemCount - 1);
|
||||
if (index != Index)
|
||||
{
|
||||
Index = index;
|
||||
|
@ -18,6 +18,12 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the selection should wrap around when reaching the edge.
|
||||
/// Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool WrapAround { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlight style of the selected choice.
|
||||
/// </summary>
|
||||
@ -88,7 +94,7 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
|
||||
{
|
||||
// Create the list prompt
|
||||
var prompt = new ListPrompt<T>(console, this);
|
||||
var result = await prompt.Show(Tree, cancellationToken, PageSize).ConfigureAwait(false);
|
||||
var result = await prompt.Show(Tree, cancellationToken, PageSize, WrapAround).ConfigureAwait(false);
|
||||
|
||||
if (Mode == SelectionMode.Leaf)
|
||||
{
|
||||
|
@ -211,6 +211,25 @@ public static class MultiSelectionPromptExtensions
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the selection should wrap around when reaching its edges.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="shouldWrap">Whether the selection should wrap around.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static MultiSelectionPrompt<T> WrapAround<T>(this MultiSelectionPrompt<T> obj, bool shouldWrap = true)
|
||||
where T : notnull
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.WrapAround = shouldWrap;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the highlight style of the selected choice.
|
||||
/// </summary>
|
||||
|
@ -20,6 +20,12 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T>
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the selection should wrap around when reaching the edge.
|
||||
/// Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool WrapAround { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the highlight style of the selected choice.
|
||||
/// </summary>
|
||||
@ -78,7 +84,7 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T>
|
||||
{
|
||||
// Create the list prompt
|
||||
var prompt = new ListPrompt<T>(console, this);
|
||||
var result = await prompt.Show(_tree, cancellationToken, PageSize).ConfigureAwait(false);
|
||||
var result = await prompt.Show(_tree, cancellationToken, PageSize, WrapAround).ConfigureAwait(false);
|
||||
|
||||
// Return the selected item
|
||||
return result.Items[result.Index].Data;
|
||||
|
@ -163,6 +163,25 @@ public static class SelectionPromptExtensions
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the selection should wrap around when reaching its edges.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="shouldWrap">Whether the selection should wrap around.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static SelectionPrompt<T> WrapAround<T>(this SelectionPrompt<T> obj, bool shouldWrap = true)
|
||||
where T : notnull
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
obj.WrapAround = shouldWrap;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the highlight style of the selected choice.
|
||||
/// </summary>
|
||||
|
120
test/Spectre.Console.Tests/Unit/Prompts/ListPromptStateTests.cs
Normal file
120
test/Spectre.Console.Tests/Unit/Prompts/ListPromptStateTests.cs
Normal file
@ -0,0 +1,120 @@
|
||||
namespace Spectre.Console.Tests.Unit;
|
||||
|
||||
public sealed class ListPromptStateTests
|
||||
{
|
||||
private ListPromptState<string> CreateListPromptState(int count, int pageSize, bool shouldWrap)
|
||||
=> new(Enumerable.Repeat(new ListPromptItem<string>(string.Empty), count).ToList(), pageSize, shouldWrap);
|
||||
|
||||
[Fact]
|
||||
public void Should_Have_Start_Index_Zero()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, false);
|
||||
|
||||
// When
|
||||
/* noop */
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Should_Increase_Index(bool wrap)
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, wrap);
|
||||
var index = state.Index;
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.DownArrow);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(index + 1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Should_Go_To_End(bool wrap)
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, wrap);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.End);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(99);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Clamp_Index_If_No_Wrap()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, false);
|
||||
state.Update(ConsoleKey.End);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.DownArrow);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(99);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Index_If_Wrap()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, true);
|
||||
state.Update(ConsoleKey.End);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.DownArrow);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Index_If_Wrap_And_Down()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(100, 10, true);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.UpArrow);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(99);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Index_If_Wrap_And_Page_Up()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(10, 100, true);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.PageUp);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Index_If_Wrap_And_Offset_And_Page_Down()
|
||||
{
|
||||
// Given
|
||||
var state = CreateListPromptState(10, 100, true);
|
||||
state.Update(ConsoleKey.End);
|
||||
state.Update(ConsoleKey.UpArrow);
|
||||
|
||||
// When
|
||||
state.Update(ConsoleKey.PageDown);
|
||||
|
||||
// Then
|
||||
state.Index.ShouldBe(8);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user