diff --git a/go.mod b/go.mod index 80b4b523a..080876810 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/cli/oauth v0.8.0 github.com/cli/safeexec v1.0.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 + github.com/creack/pty v1.1.13 github.com/gabriel-vasile/mimetype v1.1.2 github.com/google/go-cmp v0.5.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 diff --git a/go.sum b/go.sum index 7f89f8dd8..aad4401e7 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.13 h1:rTPnd/xocYRjutMfqide2zle1u96upp1gm6eUHKi7us= +github.com/creack/pty v1.1.13/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/pkg/surveyext/editor.go b/pkg/surveyext/editor.go index 21a358aa0..f868ed671 100644 --- a/pkg/surveyext/editor.go +++ b/pkg/surveyext/editor.go @@ -105,7 +105,7 @@ func (e *GhEditor) prompt(initialValue string, config *survey.PromptConfig) (int } if r == '\r' || r == '\n' { if e.BlankAllowed { - return "", nil + return initialValue, nil } else { continue } diff --git a/pkg/surveyext/editor_test.go b/pkg/surveyext/editor_test.go new file mode 100644 index 000000000..03ad27989 --- /dev/null +++ b/pkg/surveyext/editor_test.go @@ -0,0 +1,132 @@ +package surveyext + +import ( + "bytes" + "errors" + "fmt" + "os" + "strings" + "sync" + "testing" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/AlecAivazis/survey/v2/terminal" + pseudotty "github.com/creack/pty" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_GhEditor_Prompt(t *testing.T) { + e := &GhEditor{ + BlankAllowed: true, + EditorCommand: "false", + Editor: &survey.Editor{ + Message: "Body", + FileName: "*.md", + Default: "initial value", + HideDefault: true, + AppendDefault: true, + }, + } + + pty, tty, err := pseudotty.Open() + if errors.Is(err, pseudotty.ErrUnsupported) { + return + } + require.NoError(t, err) + defer pty.Close() + defer tty.Close() + + err = pseudotty.Setsize(tty, &pseudotty.Winsize{Cols: 72, Rows: 30}) + require.NoError(t, err) + + out := teeWriter{File: tty} + e.WithStdio(terminal.Stdio{ + In: tty, + Out: &out, + Err: tty, + }) + + var res string + errc := make(chan error) + + go func() { + r, err := e.Prompt(defaultPromptConfig()) + if r != nil { + res = r.(string) + } + errc <- err + }() + + time.Sleep(5 * time.Millisecond) + assert.Equal(t, "\x1b[0G\x1b[2K\x1b[0;1;92m? \x1b[0m\x1b[0;1;99mBody \x1b[0m\x1b[0;36m[(e) to launch false, enter to skip] \x1b[0m\x1b[?25l", out.String()) + fmt.Fprint(pty, "\n") // send Enter key + + err = <-errc + assert.NoError(t, err) + assert.Equal(t, "initial value", res) + assert.Equal(t, "\x1b[?25h", out.String()) +} + +// survey doesn't expose this +func defaultPromptConfig() *survey.PromptConfig { + return &survey.PromptConfig{ + PageSize: 7, + HelpInput: "?", + SuggestInput: "tab", + Icons: survey.IconSet{ + Error: survey.Icon{ + Text: "X", + Format: "red", + }, + Help: survey.Icon{ + Text: "?", + Format: "cyan", + }, + Question: survey.Icon{ + Text: "?", + Format: "green+hb", + }, + MarkedOption: survey.Icon{ + Text: "[x]", + Format: "green", + }, + UnmarkedOption: survey.Icon{ + Text: "[ ]", + Format: "default+hb", + }, + SelectFocus: survey.Icon{ + Text: ">", + Format: "cyan+b", + }, + }, + Filter: func(filter string, value string, index int) (include bool) { + filter = strings.ToLower(filter) + return strings.Contains(strings.ToLower(value), filter) + }, + KeepFilter: false, + } +} + +// teeWriter is a writer that duplicates all writes to a file into a buffer +type teeWriter struct { + *os.File + buf bytes.Buffer + mu sync.Mutex +} + +func (f *teeWriter) Write(p []byte) (n int, err error) { + f.mu.Lock() + defer f.mu.Unlock() + _, _ = f.buf.Write(p) + return f.File.Write(p) +} + +func (f *teeWriter) String() string { + f.mu.Lock() + s := f.buf.String() + f.buf.Reset() + f.mu.Unlock() + return s +}