Skip to content

Commit b223e64

Browse files
committed
Add Vim navigation to stack switch
1 parent 5da91f8 commit b223e64

2 files changed

Lines changed: 69 additions & 16 deletions

File tree

cmd/switch.go

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package cmd
22

33
import (
44
"fmt"
5+
"strings"
56

6-
"github.com/cli/go-gh/v2/pkg/prompter"
7+
"github.com/AlecAivazis/survey/v2"
8+
"github.com/cli/go-gh/v2/pkg/text"
79
"github.com/github/gh-stack/internal/config"
810
"github.com/github/gh-stack/internal/git"
911
"github.com/spf13/cobra"
@@ -17,11 +19,11 @@ func SwitchCmd(cfg *config.Config) *cobra.Command {
1719
and switch to the selected one.
1820
1921
Branches are displayed from top (furthest from trunk) to bottom (closest to
20-
trunk) with their position number. Use the arrow keys to navigate and Enter
21-
to select.
22+
trunk) with their position number. Use the down/up arrow keys or j/k to
23+
navigate and Enter to select.
2224
23-
To move one branch up or down without an interactive picker, use
24-
'gh stack up' or 'gh stack down' instead.`,
25+
To move one branch down or up without an interactive picker, use
26+
'gh stack down' or 'gh stack up' instead.`,
2527
Example: ` # Open the branch picker for the current stack
2628
$ gh stack switch`,
2729
Args: cobra.NoArgs,
@@ -61,17 +63,7 @@ func runSwitch(cfg *config.Config) error {
6163
}
6264
}
6365

64-
var selectFn func(prompt, def string, opts []string) (int, error)
65-
if cfg.SelectFn != nil {
66-
selectFn = cfg.SelectFn
67-
} else {
68-
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
69-
selectFn = func(prompt, def string, opts []string) (int, error) {
70-
return p.Select(prompt, def, opts)
71-
}
72-
}
73-
74-
selected, err := selectFn("Select a branch in the stack to switch to:", defaultOpt, options)
66+
selected, err := selectSwitchBranch(cfg, "Select a branch in the stack to switch to:", defaultOpt, options)
7567
if err != nil {
7668
if isInterruptError(err) {
7769
clearSelectPrompt(cfg, len(options))
@@ -103,3 +95,46 @@ func runSwitch(cfg *config.Config) error {
10395
cfg.Successf("Switched to %s", targetBranch)
10496
return nil
10597
}
98+
99+
func selectSwitchBranch(cfg *config.Config, prompt, defaultValue string, options []string) (int, error) {
100+
if cfg.SelectFn != nil {
101+
return cfg.SelectFn(prompt, defaultValue, options)
102+
}
103+
104+
var selected int
105+
err := survey.AskOne(
106+
newSwitchSelect(prompt, defaultValue, options),
107+
&selected,
108+
survey.WithStdio(cfg.In, cfg.Out, cfg.Err),
109+
)
110+
if err != nil {
111+
return 0, fmt.Errorf("could not prompt: %w", err)
112+
}
113+
return selected, nil
114+
}
115+
116+
func newSwitchSelect(prompt, defaultValue string, options []string) *survey.Select {
117+
selectPrompt := &survey.Select{
118+
Message: prompt,
119+
Options: options,
120+
PageSize: selectPromptPageSize,
121+
VimMode: true,
122+
Filter: switchSelectFilter,
123+
}
124+
if defaultValue != "" {
125+
for _, option := range options {
126+
if option == defaultValue {
127+
selectPrompt.Default = defaultValue
128+
break
129+
}
130+
}
131+
}
132+
return selectPrompt
133+
}
134+
135+
func switchSelectFilter(filter, value string, _ int) bool {
136+
filter = strings.ToLower(filter)
137+
value = strings.ToLower(value)
138+
return strings.Contains(value, filter) ||
139+
strings.Contains(text.RemoveDiacritics(value), filter)
140+
}

cmd/switch_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,21 @@ func TestSwitch_CmdIntegration(t *testing.T) {
271271
err := cmd.Execute()
272272
assert.NoError(t, err)
273273
}
274+
275+
func TestNewSwitchSelect_EnablesVimNavigation(t *testing.T) {
276+
options := []string{"3. résumé", "2. b2", "1. b1"}
277+
prompt := newSwitchSelect("Select a branch:", "2. b2", options)
278+
279+
assert.True(t, prompt.VimMode)
280+
assert.Equal(t, selectPromptPageSize, prompt.PageSize)
281+
assert.Equal(t, "2. b2", prompt.Default)
282+
assert.Equal(t, options, prompt.Options)
283+
require.NotNil(t, prompt.Filter)
284+
assert.True(t, prompt.Filter("resume", options[0], 0), "filter should retain diacritic-insensitive matching")
285+
}
286+
287+
func TestSwitchCmd_DescribesVimNavigation(t *testing.T) {
288+
cfg, _, _ := config.NewTestConfig()
289+
290+
assert.Contains(t, SwitchCmd(cfg).Long, "down/up arrow keys or j/k")
291+
}

0 commit comments

Comments
 (0)