mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 00:42:51 +08:00
parent
fa553fd72e
commit
865552c3f2
24
src/Spectre.Console/Extensions/StackExtensions.cs
Normal file
24
src/Spectre.Console/Extensions/StackExtensions.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class StackExtensions
|
||||
{
|
||||
public static void PushRange<T>(this Stack<T> stack, IEnumerable<T> source)
|
||||
{
|
||||
if (stack is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stack));
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
stack.Push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,11 @@ namespace Spectre.Console
|
||||
public interface IMultiSelectionItem<T> : ISelectionItem<T>
|
||||
where T : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this item is selected.
|
||||
/// </summary>
|
||||
bool IsSelected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Selects the item.
|
||||
/// </summary>
|
||||
|
@ -9,7 +9,7 @@ namespace Spectre.Console
|
||||
public ListPromptItem<T>? Parent { get; }
|
||||
public List<ListPromptItem<T>> Children { get; }
|
||||
public int Depth { get; }
|
||||
public bool Selected { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public bool IsGroup => Children.Count > 0;
|
||||
|
||||
@ -23,7 +23,7 @@ namespace Spectre.Console
|
||||
|
||||
public IMultiSelectionItem<T> Select()
|
||||
{
|
||||
Selected = true;
|
||||
IsSelected = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console
|
||||
@ -6,10 +7,29 @@ namespace Spectre.Console
|
||||
where T : notnull
|
||||
{
|
||||
private readonly List<ListPromptItem<T>> _roots;
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
|
||||
public ListPromptTree()
|
||||
public ListPromptTree(IEqualityComparer<T> comparer)
|
||||
{
|
||||
_roots = new List<ListPromptItem<T>>();
|
||||
_comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
|
||||
}
|
||||
|
||||
public ListPromptItem<T>? Find(T item)
|
||||
{
|
||||
var stack = new Stack<ListPromptItem<T>>(_roots);
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var current = stack.Pop();
|
||||
if (_comparer.Equals(item, current.Data))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
stack.PushRange(current.Children);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Add(ListPromptItem<T> node)
|
||||
|
@ -13,8 +13,6 @@ namespace Spectre.Console
|
||||
public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrategy<T>
|
||||
where T : notnull
|
||||
{
|
||||
private readonly ListPromptTree<T> _tree;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title.
|
||||
/// </summary>
|
||||
@ -59,12 +57,18 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public SelectionMode Mode { get; set; } = SelectionMode.Leaf;
|
||||
|
||||
internal ListPromptTree<T> Tree { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiSelectionPrompt{T}"/> class.
|
||||
/// </summary>
|
||||
public MultiSelectionPrompt()
|
||||
/// <param name="comparer">
|
||||
/// The <see cref="IEqualityComparer{T}"/> implementation to use when comparing items,
|
||||
/// or <c>null</c> to use the default <see cref="IEqualityComparer{T}"/> for the type of the item.
|
||||
/// </param>
|
||||
public MultiSelectionPrompt(IEqualityComparer<T>? comparer = null)
|
||||
{
|
||||
_tree = new ListPromptTree<T>();
|
||||
Tree = new ListPromptTree<T>(comparer ?? EqualityComparer<T>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -75,7 +79,7 @@ namespace Spectre.Console
|
||||
public IMultiSelectionItem<T> AddChoice(T item)
|
||||
{
|
||||
var node = new ListPromptItem<T>(item);
|
||||
_tree.Add(node);
|
||||
Tree.Add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -84,18 +88,18 @@ namespace Spectre.Console
|
||||
{
|
||||
// Create the list prompt
|
||||
var prompt = new ListPrompt<T>(console, this);
|
||||
var result = prompt.Show(_tree, PageSize);
|
||||
var result = prompt.Show(Tree, PageSize);
|
||||
|
||||
if (Mode == SelectionMode.Leaf)
|
||||
{
|
||||
return result.Items
|
||||
.Where(x => x.Selected && x.Children.Count == 0)
|
||||
.Where(x => x.IsSelected && x.Children.Count == 0)
|
||||
.Select(x => x.Data)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return result.Items
|
||||
.Where(x => x.Selected)
|
||||
.Where(x => x.IsSelected)
|
||||
.Select(x => x.Data)
|
||||
.ToList();
|
||||
}
|
||||
@ -105,7 +109,7 @@ namespace Spectre.Console
|
||||
{
|
||||
if (key.Key == ConsoleKey.Enter)
|
||||
{
|
||||
if (Required && state.Items.None(x => x.Selected))
|
||||
if (Required && state.Items.None(x => x.IsSelected))
|
||||
{
|
||||
// Selection not permitted
|
||||
return ListPromptInputResult.None;
|
||||
@ -118,14 +122,14 @@ namespace Spectre.Console
|
||||
if (key.Key == ConsoleKey.Spacebar)
|
||||
{
|
||||
var current = state.Items[state.Index];
|
||||
var select = !current.Selected;
|
||||
var select = !current.IsSelected;
|
||||
|
||||
if (Mode == SelectionMode.Leaf)
|
||||
{
|
||||
// Select the node and all it's children
|
||||
foreach (var item in current.Traverse(includeSelf: true))
|
||||
{
|
||||
item.Selected = select;
|
||||
item.IsSelected = select;
|
||||
}
|
||||
|
||||
// Visit every parent and evaluate if it's selection
|
||||
@ -133,13 +137,13 @@ namespace Spectre.Console
|
||||
var parent = current.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.Selected = parent.Traverse(includeSelf: false).All(x => x.Selected);
|
||||
parent.IsSelected = parent.Traverse(includeSelf: false).All(x => x.IsSelected);
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current.Selected = !current.Selected;
|
||||
current.IsSelected = !current.IsSelected;
|
||||
}
|
||||
|
||||
// Refresh the list
|
||||
@ -209,7 +213,7 @@ namespace Spectre.Console
|
||||
text = text.RemoveMarkup();
|
||||
}
|
||||
|
||||
var checkbox = item.Node.Selected
|
||||
var checkbox = item.Node.IsSelected
|
||||
? (item.Node.IsGroup && Mode == SelectionMode.Leaf
|
||||
? ListPromptConstants.GroupSelectedCheckbox : ListPromptConstants.SelectedCheckbox)
|
||||
: ListPromptConstants.Checkbox;
|
||||
|
@ -130,40 +130,19 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="index">The index of the item to select.</param>
|
||||
/// <param name="item">The item to select.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
[Obsolete("Selection by index has been made obsolete", error: true)]
|
||||
public static MultiSelectionPrompt<T> Select<T>(this MultiSelectionPrompt<T> obj, int index)
|
||||
public static MultiSelectionPrompt<T> Select<T>(this MultiSelectionPrompt<T> obj, T item)
|
||||
where T : notnull
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
if (obj is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks multiple items as selected.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="indices">The indices of the items to select.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
[Obsolete("Selection by index has been made obsolete", error: true)]
|
||||
public static MultiSelectionPrompt<T> Select<T>(this MultiSelectionPrompt<T> obj, params int[] indices)
|
||||
where T : notnull
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
var node = obj.Tree.Find(item);
|
||||
node?.Select();
|
||||
|
||||
/// <summary>
|
||||
/// Marks multiple items as selected.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The prompt result type.</typeparam>
|
||||
/// <param name="obj">The prompt.</param>
|
||||
/// <param name="indices">The indices of the items to select.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
[Obsolete("Selection by index has been made obsolete", error: true)]
|
||||
public static MultiSelectionPrompt<T> Select<T>(this MultiSelectionPrompt<T> obj, IEnumerable<int> indices)
|
||||
where T : notnull
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public SelectionPrompt()
|
||||
{
|
||||
_tree = new ListPromptTree<T>();
|
||||
_tree = new ListPromptTree<T>(EqualityComparer<T>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class MultiSelectionPromptTests
|
||||
{
|
||||
private class CustomItem
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
|
||||
public class Comparer : IEqualityComparer<CustomItem>
|
||||
{
|
||||
public bool Equals(CustomItem x, CustomItem y)
|
||||
{
|
||||
return x.X == y.X && x.Y == y.Y;
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] CustomItem obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Mark_Item_As_Selected_By_Default()
|
||||
{
|
||||
// Given
|
||||
var prompt = new MultiSelectionPrompt<int>();
|
||||
|
||||
// When
|
||||
var choice = prompt.AddChoice(32);
|
||||
|
||||
// Then
|
||||
choice.IsSelected.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Mark_Item_As_Selected()
|
||||
{
|
||||
// Given
|
||||
var prompt = new MultiSelectionPrompt<int>();
|
||||
var choice = prompt.AddChoice(32);
|
||||
|
||||
// When
|
||||
prompt.Select(32);
|
||||
|
||||
// Then
|
||||
choice.IsSelected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Mark_Custom_Item_As_Selected_If_The_Same_Reference_Is_Used()
|
||||
{
|
||||
// Given
|
||||
var prompt = new MultiSelectionPrompt<CustomItem>();
|
||||
var item = new CustomItem { X = 18, Y = 32 };
|
||||
var choice = prompt.AddChoice(item);
|
||||
|
||||
// When
|
||||
prompt.Select(item);
|
||||
|
||||
// Then
|
||||
choice.IsSelected.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Mark_Custom_Item_As_Selected_If_A_Comparer_Is_Provided()
|
||||
{
|
||||
// Given
|
||||
var prompt = new MultiSelectionPrompt<CustomItem>(new CustomItem.Comparer());
|
||||
var choice = prompt.AddChoice(new CustomItem { X = 18, Y = 32 });
|
||||
|
||||
// When
|
||||
prompt.Select(new CustomItem { X = 18, Y = 32 });
|
||||
|
||||
// Then
|
||||
choice.IsSelected.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user