Skip to content

Commit 7b883ec

Browse files
Support J and K for navigating list prompts
1 parent 0889c2f commit 7b883ec

3 files changed

Lines changed: 73 additions & 16 deletions

File tree

src/Spectre.Console/Prompts/List/ListPromptState.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public bool Update(ConsoleKeyInfo keyInfo)
6363
switch (keyInfo.Key)
6464
{
6565
case ConsoleKey.UpArrow:
66+
case ConsoleKey.K:
6667
if (currentLeafIndex > 0)
6768
{
6869
index = _leafIndexes[currentLeafIndex - 1];
@@ -75,6 +76,7 @@ public bool Update(ConsoleKeyInfo keyInfo)
7576
break;
7677

7778
case ConsoleKey.DownArrow:
79+
case ConsoleKey.J:
7880
if (currentLeafIndex < _leafIndexes.Count - 1)
7981
{
8082
index = _leafIndexes[currentLeafIndex + 1];
@@ -117,8 +119,8 @@ public bool Update(ConsoleKeyInfo keyInfo)
117119
{
118120
index = keyInfo.Key switch
119121
{
120-
ConsoleKey.UpArrow => Index - 1,
121-
ConsoleKey.DownArrow => Index + 1,
122+
ConsoleKey.UpArrow or ConsoleKey.K => Index - 1,
123+
ConsoleKey.DownArrow or ConsoleKey.J => Index + 1,
122124
ConsoleKey.Home => 0,
123125
ConsoleKey.End => ItemCount - 1,
124126
ConsoleKey.PageUp => Index - PageSize,

src/Tests/Spectre.Console.Cli.Tests/Unit/Testing/InteractiveCommandTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,35 @@ public void InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput()
7777
result.ExitCode.ShouldBe(0);
7878
result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
7979
}
80+
81+
[Fact]
82+
public void InteractiveCommand_WithMockedUserInputs_VimMotions_ProducesExpectedOutput()
83+
{
84+
// Given
85+
TestConsole console = new();
86+
console.Interactive();
87+
88+
// Your mocked inputs must always end with "Enter" for each prompt!
89+
90+
// Multi selection prompt: Choose first option
91+
console.Input.PushKey(ConsoleKey.Spacebar);
92+
console.Input.PushKey(ConsoleKey.Enter);
93+
94+
// Selection prompt: Choose second option
95+
console.Input.PushKey(ConsoleKey.J);
96+
console.Input.PushKey(ConsoleKey.Enter);
97+
98+
// Ask text prompt: Enter name
99+
console.Input.PushTextWithEnter("Spectre Console");
100+
101+
var app = new CommandAppTester(null, new CommandAppTesterSettings(), console);
102+
app.SetDefaultCommand<InteractiveCommand>();
103+
104+
// When
105+
var result = app.Run();
106+
107+
// Then
108+
result.ExitCode.ShouldBe(0);
109+
result.Output.EndsWith("[Apple;Apricot;Spectre Console]");
110+
}
80111
}

src/Tests/Spectre.Console.Tests/Unit/Prompts/ListPromptStateTests.cs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,33 @@ public void Should_Have_Start_Index_Zero()
2121
state.Index.ShouldBe(0);
2222
}
2323

24+
[Theory]
25+
[InlineData(ConsoleKey.DownArrow, true)]
26+
[InlineData(ConsoleKey.J, false)]
27+
public void Should_Increase_Index(ConsoleKey key, bool wrap)
28+
{
29+
// Given
30+
var state = CreateListPromptState(100, 10, wrap, false);
31+
var index = state.Index;
32+
33+
// When
34+
state.Update(key.ToConsoleKeyInfo());
35+
36+
// Then
37+
state.Index.ShouldBe(index + 1);
38+
}
39+
2440
[Theory]
2541
[InlineData(true)]
2642
[InlineData(false)]
27-
public void Should_Increase_Index(bool wrap)
43+
public void Should_Increase_Index_VimMotion(bool wrap)
2844
{
2945
// Given
3046
var state = CreateListPromptState(100, 10, wrap, false);
3147
var index = state.Index;
3248

3349
// When
34-
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo());
50+
state.Update(ConsoleKey.J.ToConsoleKeyInfo());
3551

3652
// Then
3753
state.Index.ShouldBe(index + 1);
@@ -52,42 +68,48 @@ public void Should_Go_To_End(bool wrap)
5268
state.Index.ShouldBe(99);
5369
}
5470

55-
[Fact]
56-
public void Should_Clamp_Index_If_No_Wrap()
71+
[Theory]
72+
[InlineData(ConsoleKey.DownArrow)]
73+
[InlineData(ConsoleKey.J)]
74+
public void Should_Clamp_Index_If_No_Wrap(ConsoleKey key)
5775
{
5876
// Given
5977
var state = CreateListPromptState(100, 10, false, false);
6078
state.Update(ConsoleKey.End.ToConsoleKeyInfo());
6179

6280
// When
63-
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo());
81+
state.Update(key.ToConsoleKeyInfo());
6482

6583
// Then
6684
state.Index.ShouldBe(99);
6785
}
6886

69-
[Fact]
70-
public void Should_Wrap_Index_If_Wrap()
87+
[Theory]
88+
[InlineData(ConsoleKey.DownArrow)]
89+
[InlineData(ConsoleKey.J)]
90+
public void Should_Wrap_Index_If_Wrap(ConsoleKey key)
7191
{
7292
// Given
7393
var state = CreateListPromptState(100, 10, true, false);
7494
state.Update(ConsoleKey.End.ToConsoleKeyInfo());
7595

7696
// When
77-
state.Update(ConsoleKey.DownArrow.ToConsoleKeyInfo());
97+
state.Update(key.ToConsoleKeyInfo());
7898

7999
// Then
80100
state.Index.ShouldBe(0);
81101
}
82102

83-
[Fact]
84-
public void Should_Wrap_Index_If_Wrap_And_Down()
103+
[Theory]
104+
[InlineData(ConsoleKey.UpArrow)]
105+
[InlineData(ConsoleKey.K)]
106+
public void Should_Wrap_Index_If_Wrap_And_Down(ConsoleKey key)
85107
{
86108
// Given
87109
var state = CreateListPromptState(100, 10, true, false);
88110

89111
// When
90-
state.Update(ConsoleKey.UpArrow.ToConsoleKeyInfo());
112+
state.Update(key.ToConsoleKeyInfo());
91113

92114
// Then
93115
state.Index.ShouldBe(99);
@@ -106,13 +128,15 @@ public void Should_Wrap_Index_If_Wrap_And_Page_Up()
106128
state.Index.ShouldBe(0);
107129
}
108130

109-
[Fact]
110-
public void Should_Wrap_Index_If_Wrap_And_Offset_And_Page_Down()
131+
[Theory]
132+
[InlineData(ConsoleKey.UpArrow)]
133+
[InlineData(ConsoleKey.K)]
134+
public void Should_Wrap_Index_If_Wrap_And_Offset_And_Page_Down(ConsoleKey key)
111135
{
112136
// Given
113137
var state = CreateListPromptState(10, 100, true, false);
114138
state.Update(ConsoleKey.End.ToConsoleKeyInfo());
115-
state.Update(ConsoleKey.UpArrow.ToConsoleKeyInfo());
139+
state.Update(key.ToConsoleKeyInfo());
116140

117141
// When
118142
state.Update(ConsoleKey.PageDown.ToConsoleKeyInfo());

0 commit comments

Comments
 (0)