Skip to content

Commit a962898

Browse files
committed
replace deprecated survey pkg with bubbletea-based huh for interactive prompts
1 parent de00856 commit a962898

15 files changed

Lines changed: 352 additions & 127 deletions

File tree

cmd/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package cmd
33
import (
44
"fmt"
55

6-
"github.com/cli/go-gh/v2/pkg/prompter"
6+
"github.com/github/gh-stack/internal/prompter"
77
"github.com/github/gh-stack/internal/branch"
88
"github.com/github/gh-stack/internal/config"
99
"github.com/github/gh-stack/internal/git"

cmd/checkout.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88

99
"github.com/cli/go-gh/v2/pkg/api"
10-
"github.com/cli/go-gh/v2/pkg/prompter"
10+
"github.com/github/gh-stack/internal/prompter"
1111
"github.com/github/gh-stack/internal/config"
1212
"github.com/github/gh-stack/internal/git"
1313
"github.com/github/gh-stack/internal/github"
@@ -376,7 +376,6 @@ func handleCompositionConflict(
376376
selected, err := p.Select("How would you like to resolve this?", "", options)
377377
if err != nil {
378378
if isInterruptError(err) {
379-
clearSelectPrompt(cfg, len(options))
380379
printInterrupt(cfg)
381380
return nil, errInterrupt
382381
}
@@ -574,7 +573,6 @@ func interactiveStackPicker(cfg *config.Config, sf *stack.StackFile) (*stack.Sta
574573
)
575574
if err != nil {
576575
if isInterruptError(err) {
577-
clearSelectPrompt(cfg, len(options))
578576
printInterrupt(cfg)
579577
return nil, errInterrupt
580578
}

cmd/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8-
"github.com/cli/go-gh/v2/pkg/prompter"
8+
"github.com/github/gh-stack/internal/prompter"
99
"github.com/github/gh-stack/internal/branch"
1010
"github.com/github/gh-stack/internal/config"
1111
"github.com/github/gh-stack/internal/git"

cmd/merge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55

66
"github.com/cli/go-gh/v2/pkg/browser"
7-
"github.com/cli/go-gh/v2/pkg/prompter"
7+
"github.com/github/gh-stack/internal/prompter"
88
"github.com/github/gh-stack/internal/config"
99
"github.com/github/gh-stack/internal/stack"
1010
"github.com/spf13/cobra"

cmd/push.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"errors"
55
"fmt"
66

7-
"github.com/cli/go-gh/v2/pkg/prompter"
7+
"github.com/github/gh-stack/internal/prompter"
88
"github.com/github/gh-stack/internal/config"
99
"github.com/github/gh-stack/internal/git"
1010
"github.com/github/gh-stack/internal/modify"
@@ -151,7 +151,6 @@ func pickRemote(cfg *config.Config, branch, remoteOverride string) (string, erro
151151
selected, promptErr := p.Select("Multiple remotes found. Which remote should be used?", "", multi.Remotes)
152152
if promptErr != nil {
153153
if isInterruptError(promptErr) {
154-
clearSelectPrompt(cfg, len(multi.Remotes))
155154
printInterrupt(cfg)
156155
return "", errInterrupt
157156
}

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ func Execute() {
6969
wrapCmd.SetArgs(append([]string{"stack"}, os.Args[1:]...))
7070

7171
if err := wrapCmd.Execute(); err != nil {
72+
if errors.Is(err, errInterrupt) {
73+
os.Exit(1)
74+
}
7275
var exitErr *ExitError
7376
if errors.As(err, &exitErr) {
7477
os.Exit(exitErr.Code)

cmd/submit.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88

99
"github.com/cli/go-gh/v2/pkg/api"
10-
"github.com/cli/go-gh/v2/pkg/prompter"
10+
"github.com/github/gh-stack/internal/prompter"
1111
"github.com/github/gh-stack/internal/config"
1212
"github.com/github/gh-stack/internal/git"
1313
"github.com/github/gh-stack/internal/github"
@@ -84,8 +84,12 @@ func runSubmit(cfg *config.Config, opts *submitOptions) error {
8484
if _, err := client.ListStacks(); err != nil {
8585
cfg.Warningf("Stacked PRs are not enabled for this repository")
8686
if cfg.IsInteractive() {
87-
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
88-
proceed, promptErr := p.Confirm("Would you still like to create regular PRs?", false)
87+
confirmFn := cfg.ConfirmFn
88+
if confirmFn == nil {
89+
p := prompter.New(cfg.In, cfg.Out, cfg.Err)
90+
confirmFn = p.Confirm
91+
}
92+
proceed, promptErr := confirmFn("Would you still like to create regular PRs?", false)
8993
if promptErr != nil {
9094
if isInterruptError(promptErr) {
9195
printInterrupt(cfg)

cmd/submit_test.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,15 +905,11 @@ func TestSubmit_PreflightCheck_404_Interactive_UserDeclinesAborts(t *testing.T)
905905
restore := git.SetOps(mock)
906906
defer restore()
907907

908-
// Force interactive mode; survey will fail on the pipe,
909-
// which is treated as a decline — same as user saying "no".
910-
inR, inW, _ := os.Pipe()
911-
inW.Close()
912-
defer inR.Close()
913-
914908
cfg, _, errR := config.NewTestConfig()
915-
cfg.In = inR
916909
cfg.ForceInteractive = true
910+
cfg.ConfirmFn = func(prompt string, defaultValue bool) (bool, error) {
911+
return false, nil // user declines
912+
}
917913
cfg.GitHubClientOverride = &github.MockClient{
918914
ListStacksFn: func() ([]github.RemoteStack, error) {
919915
return nil, &api.HTTPError{StatusCode: 404, Message: "Not Found"}

cmd/switch.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package cmd
33
import (
44
"fmt"
55

6-
"github.com/cli/go-gh/v2/pkg/prompter"
6+
"github.com/github/gh-stack/internal/prompter"
77
"github.com/github/gh-stack/internal/config"
88
"github.com/github/gh-stack/internal/git"
99
"github.com/spf13/cobra"
@@ -68,7 +68,6 @@ func runSwitch(cfg *config.Config) error {
6868
selected, err := selectFn("Select a branch in the stack to switch to:", defaultOpt, options)
6969
if err != nil {
7070
if isInterruptError(err) {
71-
clearSelectPrompt(cfg, len(options))
7271
printInterrupt(cfg)
7372
return errInterrupt
7473
}

cmd/utils.go

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import (
77
"strconv"
88
"strings"
99

10-
"github.com/AlecAivazis/survey/v2/terminal"
11-
"github.com/cli/go-gh/v2/pkg/prompter"
10+
"github.com/github/gh-stack/internal/prompter"
1211
"github.com/github/gh-stack/internal/config"
1312
"github.com/github/gh-stack/internal/git"
1413
"github.com/github/gh-stack/internal/github"
@@ -54,36 +53,18 @@ func (e *ExitError) Is(target error) bool {
5453
// Callers should exit silently (the friendly message is already printed).
5554
var errInterrupt = errors.New("interrupt")
5655

57-
// isInterruptError reports whether err is (or wraps) the survey interrupt,
56+
// isInterruptError reports whether err is (or wraps) the prompt interrupt,
5857
// which is raised when the user presses Ctrl+C during a prompt.
5958
func isInterruptError(err error) bool {
60-
return errors.Is(err, terminal.InterruptErr)
59+
return errors.Is(err, prompter.ErrInterrupt)
6160
}
6261

6362
// printInterrupt prints a friendly message and should be called exactly once
64-
// per interrupted operation. The leading newline ensures the message starts
65-
// on its own line even if the cursor was mid-prompt.
63+
// per interrupted operation.
6664
func printInterrupt(cfg *config.Config) {
67-
fmt.Fprintln(cfg.Err)
6865
cfg.Infof("Received interrupt, aborting operation")
6966
}
7067

71-
// selectPromptPageSize matches the PageSize used by the go-gh prompter.
72-
const selectPromptPageSize = 20
73-
74-
// clearSelectPrompt erases the rendered Select prompt from the terminal.
75-
// survey/v2 does not call Cleanup on interrupt, leaving the question and
76-
// option lines visible. This function moves the cursor up past those lines
77-
// and clears to the end of the screen.
78-
func clearSelectPrompt(cfg *config.Config, numOptions int) {
79-
visible := numOptions
80-
if visible > selectPromptPageSize {
81-
visible = selectPromptPageSize
82-
}
83-
// 1 line for the question/filter + visible option lines
84-
lines := 1 + visible
85-
fmt.Fprintf(cfg.Out, "\033[%dA\033[J", lines)
86-
}
8768

8869
// loadStackResult holds everything returned by loadStack.
8970
type loadStackResult struct {
@@ -206,7 +187,6 @@ func resolveStack(sf *stack.StackFile, branch string, cfg *config.Config) (*stac
206187
selected, err := p.Select("Which stack would you like to use?", "", options)
207188
if err != nil {
208189
if isInterruptError(err) {
209-
clearSelectPrompt(cfg, len(options))
210190
printInterrupt(cfg)
211191
return nil, errInterrupt
212192
}

0 commit comments

Comments
 (0)