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

View File

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