Support J and K for navigating list prompts (#1877)

This commit is contained in:
Tobias Tengler
2025-08-13 18:23:26 +02:00
committed by GitHub
parent 0889c2f97c
commit a8b2f1f1e0
3 changed files with 78 additions and 18 deletions

View File

@@ -63,6 +63,7 @@ internal sealed class ListPromptState<T>
switch (keyInfo.Key) switch (keyInfo.Key)
{ {
case ConsoleKey.UpArrow: case ConsoleKey.UpArrow:
case ConsoleKey.K:
if (currentLeafIndex > 0) if (currentLeafIndex > 0)
{ {
index = _leafIndexes[currentLeafIndex - 1]; index = _leafIndexes[currentLeafIndex - 1];
@@ -75,6 +76,7 @@ internal sealed class ListPromptState<T>
break; break;
case ConsoleKey.DownArrow: case ConsoleKey.DownArrow:
case ConsoleKey.J:
if (currentLeafIndex < _leafIndexes.Count - 1) if (currentLeafIndex < _leafIndexes.Count - 1)
{ {
index = _leafIndexes[currentLeafIndex + 1]; index = _leafIndexes[currentLeafIndex + 1];
@@ -117,8 +119,8 @@ internal sealed class ListPromptState<T>
{ {
index = keyInfo.Key switch index = keyInfo.Key switch
{ {
ConsoleKey.UpArrow => Index - 1, ConsoleKey.UpArrow or ConsoleKey.K => Index - 1,
ConsoleKey.DownArrow => Index + 1, ConsoleKey.DownArrow or ConsoleKey.J => Index + 1,
ConsoleKey.Home => 0, ConsoleKey.Home => 0,
ConsoleKey.End => ItemCount - 1, ConsoleKey.End => ItemCount - 1,
ConsoleKey.PageUp => Index - PageSize, ConsoleKey.PageUp => Index - PageSize,

View File

@@ -77,4 +77,35 @@ public sealed class InteractiveCommandTests
result.ExitCode.ShouldBe(0); result.ExitCode.ShouldBe(0);
result.Output.EndsWith("[Apple;Apricot;Spectre Console]"); result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
} }
[Fact]
public void InteractiveCommand_WithMockedUserInputs_VimMotions_ProducesExpectedOutput()
{
// Given
TestConsole console = new();
console.Interactive();
// Your mocked inputs must always end with "Enter" for each prompt!
// Multi selection prompt: Choose first option
console.Input.PushKey(ConsoleKey.Spacebar);
console.Input.PushKey(ConsoleKey.Enter);
// Selection prompt: Choose second option
console.Input.PushKey(ConsoleKey.J);
console.Input.PushKey(ConsoleKey.Enter);
// Ask text prompt: Enter name
console.Input.PushTextWithEnter("Spectre Console");
var app = new CommandAppTester(null, new CommandAppTesterSettings(), console);
app.SetDefaultCommand<InteractiveCommand>();
// When
var result = app.Run();
// Then
result.ExitCode.ShouldBe(0);
result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
}
} }

View File

@@ -22,16 +22,35 @@ public sealed class ListPromptStateTests
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(ConsoleKey.UpArrow)]
[InlineData(false)] [InlineData(ConsoleKey.K)]
public void Should_Increase_Index(bool wrap) public void Should_Decrease_Index(ConsoleKey key)
{
// Given
var state = CreateListPromptState(100, 10, false, false);
state.Update(ConsoleKey.End.ToConsoleKeyInfo());
var index = state.Index;
// When
state.Update(key.ToConsoleKeyInfo());
// Then
state.Index.ShouldBe(index - 1);
}
[Theory]
[InlineData(ConsoleKey.DownArrow, true)]
[InlineData(ConsoleKey.DownArrow, false)]
[InlineData(ConsoleKey.J, true)]
[InlineData(ConsoleKey.J, false)]
public void Should_Increase_Index(ConsoleKey key, bool wrap)
{ {
// Given // Given
var state = CreateListPromptState(100, 10, wrap, false); var state = CreateListPromptState(100, 10, wrap, false);
var index = state.Index; var index = state.Index;
// When // When
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo()); state.Update(key.ToConsoleKeyInfo());
// Then // Then
state.Index.ShouldBe(index + 1); state.Index.ShouldBe(index + 1);
@@ -52,42 +71,48 @@ public sealed class ListPromptStateTests
state.Index.ShouldBe(99); state.Index.ShouldBe(99);
} }
[Fact] [Theory]
public void Should_Clamp_Index_If_No_Wrap() [InlineData(ConsoleKey.DownArrow)]
[InlineData(ConsoleKey.J)]
public void Should_Clamp_Index_If_No_Wrap(ConsoleKey key)
{ {
// Given // Given
var state = CreateListPromptState(100, 10, false, false); var state = CreateListPromptState(100, 10, false, false);
state.Update(ConsoleKey.End.ToConsoleKeyInfo()); state.Update(ConsoleKey.End.ToConsoleKeyInfo());
// When // When
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo()); state.Update(key.ToConsoleKeyInfo());
// Then // Then
state.Index.ShouldBe(99); state.Index.ShouldBe(99);
} }
[Fact] [Theory]
public void Should_Wrap_Index_If_Wrap() [InlineData(ConsoleKey.DownArrow)]
[InlineData(ConsoleKey.J)]
public void Should_Wrap_Index_If_Wrap(ConsoleKey key)
{ {
// Given // Given
var state = CreateListPromptState(100, 10, true, false); var state = CreateListPromptState(100, 10, true, false);
state.Update(ConsoleKey.End.ToConsoleKeyInfo()); state.Update(ConsoleKey.End.ToConsoleKeyInfo());
// When // When
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo()); state.Update(key.ToConsoleKeyInfo());
// Then // Then
state.Index.ShouldBe(0); state.Index.ShouldBe(0);
} }
[Fact] [Theory]
public void Should_Wrap_Index_If_Wrap_And_Down() [InlineData(ConsoleKey.UpArrow)]
[InlineData(ConsoleKey.K)]
public void Should_Wrap_Index_If_Wrap_And_Down(ConsoleKey key)
{ {
// Given // Given
var state = CreateListPromptState(100, 10, true, false); var state = CreateListPromptState(100, 10, true, false);
// When // When
state.Update(ConsoleKey.UpArrow.ToConsoleKeyInfo()); state.Update(key.ToConsoleKeyInfo());
// Then // Then
state.Index.ShouldBe(99); state.Index.ShouldBe(99);
@@ -106,13 +131,15 @@ public sealed class ListPromptStateTests
state.Index.ShouldBe(0); state.Index.ShouldBe(0);
} }
[Fact] [Theory]
public void Should_Wrap_Index_If_Wrap_And_Offset_And_Page_Down() [InlineData(ConsoleKey.UpArrow)]
[InlineData(ConsoleKey.K)]
public void Should_Wrap_Index_If_Wrap_And_Offset_And_Page_Down(ConsoleKey key)
{ {
// Given // Given
var state = CreateListPromptState(10, 100, true, false); var state = CreateListPromptState(10, 100, true, false);
state.Update(ConsoleKey.End.ToConsoleKeyInfo()); state.Update(ConsoleKey.End.ToConsoleKeyInfo());
state.Update(ConsoleKey.UpArrow.ToConsoleKeyInfo()); state.Update(key.ToConsoleKeyInfo());
// When // When
state.Update(ConsoleKey.PageDown.ToConsoleKeyInfo()); state.Update(ConsoleKey.PageDown.ToConsoleKeyInfo());