Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow letter keys (A-Z) to cycle indexes in Prompts #996

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
45 changes: 39 additions & 6 deletions src/Spectre.Console/Prompts/List/ListPromptState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,48 @@ public ListPromptState(IReadOnlyList<ListPromptItem<T>> items, int pageSize, boo

public bool Update(ConsoleKey key)
{
var index = key switch
var index = Index;

// if user presses a letter key
if (key >= ConsoleKey.A && key <= ConsoleKey.Z)
{
var keyStruck = new string((char)key, 1);

// find indexes of items that start with the letter
int[] matchingIndexes = Items.Select((item, idx) => (item, idx))
.Where(k => k.item.Data.ToString()?.StartsWith(keyStruck, StringComparison.InvariantCultureIgnoreCase) ?? false)
Copy link
Contributor

@thoemmi thoemmi Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k.item.Data must not be string but can be any type of object. You have to respect a potential converter. See how the data is rendered e.g. in SelectionPrompt:

var text = (Converter ?? TypeConverterHelper.ConvertToString)?.Invoke(item.Node.Data) ?? item.Node.Data.ToString() ?? "?";

When you want to jump to an item with the specific starting character, you have to look for the string that is actually displayed.

Copy link
Author

@tznind tznind Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out, I had missed that. I have added the converter arg to ListPromptState<T>.Update method so it can check the actual string contents. I added it as a default parameter to the method but if you would rather I create a new overload instead then I can update to that approach.

I had to add the converter property to IListPromptStrategy<T> since that is what ListPrompt.cs has access to. And ListPrompt is the one calling state.Update.

.Select(k => k.idx)
.ToArray();

// if there are items beginning with this letter
if (matchingIndexes.Length > 0)
{
// is one of them currently selected?
var currentlySelected = Array.IndexOf(matchingIndexes, index);

if (currentlySelected == -1)
{
// we are not currently selecting any item beginning with the struck key
// so jump to first item in list that begins with the letter
index = matchingIndexes[0];
}
else
{
// cycle to next (circular)
index = matchingIndexes[(currentlySelected + 1) % matchingIndexes.Length];
}
}
}

index = key switch
{
ConsoleKey.UpArrow => Index - 1,
ConsoleKey.DownArrow => Index + 1,
ConsoleKey.UpArrow => index - 1,
ConsoleKey.DownArrow => index + 1,
ConsoleKey.Home => 0,
ConsoleKey.End => ItemCount - 1,
ConsoleKey.PageUp => Index - PageSize,
ConsoleKey.PageDown => Index + PageSize,
_ => Index,
ConsoleKey.PageUp => index - PageSize,
ConsoleKey.PageDown => index + PageSize,
_ => index,
};

index = WrapAround
Expand Down
36 changes: 36 additions & 0 deletions test/Spectre.Console.Tests/Unit/Prompts/ListPromptStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ public void Should_Increase_Index(bool wrap)
state.Index.ShouldBe(index + 1);
}

[Fact]
public void LetterJumpToSelection()
{
// Given
var state = new ListPromptState<string>(
new[]
{
new ListPromptItem<string>("apple"),
new ListPromptItem<string>("bannana"),
new ListPromptItem<string>("fish"),
new ListPromptItem<string>("flamingo"),
}
.ToList(), 3, true);

// First item should be selected
state.Index.ShouldBe(0);

// When user presses F
state.Update(ConsoleKey.F);

// Then should jump to fish
state.Index.ShouldBe(2);

// When user presses F again
state.Update(ConsoleKey.F);

// Then should jump to flamingo
state.Index.ShouldBe(3);

// When user presses F third time
state.Update(ConsoleKey.F);

// Then should cycle back to fish
state.Index.ShouldBe(2);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down