diff --git a/cmd/glab/main.go b/cmd/glab/main.go index edcf3926e83d948ed6cea172c11624a46ca4fad9..ac2e0a6ad18d85b9fc072d4d87ada82485be8d39 100644 --- a/cmd/glab/main.go +++ b/cmd/glab/main.go @@ -9,10 +9,8 @@ import ( "runtime" "strconv" - surveyCore "github.com/AlecAivazis/survey/v2/core" "github.com/charmbracelet/fang" "github.com/charmbracelet/lipgloss/v2" - "github.com/mgutz/ansi" "github.com/spf13/cobra" "gitlab.com/gitlab-org/cli/internal/api" @@ -125,8 +123,6 @@ func main() { api.BuildInfo{Version: version, Commit: commit, Platform: platform, Architecture: runtime.GOARCH}, ) - setupSurveyCore(cmdFactory.IO()) - // Setup command var expandedArgs []string if len(os.Args) > 0 { @@ -207,28 +203,6 @@ func main() { } } -func setupSurveyCore(io *iostreams.IOStreams) { - if !io.ColorEnabled() { - surveyCore.DisableColor = true - } else { - // Override survey's choice of color for default values - // For default values for e.g. `Input` prompts, Survey uses the literal "white" color, - // which makes no sense on dark terminals and is literally invisible on light backgrounds. - // This overrides Survey to output a gray color for 256-color terminals and "default" for basic terminals. - surveyCore.TemplateFuncsWithColor["color"] = func(style string) string { - switch style { - case "white": - if io.Is256ColorSupported() { - return fmt.Sprintf("\x1b[%d;5;%dm", 38, 242) - } - return ansi.ColorCode("default") - default: - return ansi.ColorCode(style) - } - } - } -} - func setupTelemetryHook(cfg config.Config, f cmdutils.Factory, cmd *cobra.Command) { if isTelemetryEnabled(cfg) { cobra.OnFinalize(addTelemetryHook(f, cmd)) diff --git a/go.mod b/go.mod index 5cc733a527d99a7597a36d904a72dd0de6280758..e76ddb74cd9061332081ea56c11c00c7eda9661e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ go 1.25.4 replace github.com/survivorbat/huhtest => github.com/timofurrer/huhtest v0.0.0-20250922072747-46976a734937 require ( - github.com/AlecAivazis/survey/v2 v2.3.7 github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/adrg/xdg v0.5.3 @@ -29,7 +28,6 @@ require ( github.com/gosuri/uilive v0.0.4 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.8.0 - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/lunixbochs/vtclean v1.0.0 github.com/mark3labs/mcp-go v0.43.2 github.com/mattn/go-colorable v0.1.14 diff --git a/go.sum b/go.sum index c22f78237f81ee227ecc1e55771abe0fad1b169c..a4256442d2e787f818886a8693ede4ee2b96e0d8 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,11 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= -github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -92,7 +88,6 @@ github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9 github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= @@ -182,8 +177,6 @@ github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bP github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= @@ -192,8 +185,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -211,10 +202,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I= github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -224,7 +213,6 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mazznoer/csscolorparser v0.1.5 h1:Wr4uNIE+pHWN3TqZn2SGpA2nLRG064gB7WdSfSS5cz4= github.com/mazznoer/csscolorparser v0.1.5/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -303,7 +291,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -379,7 +366,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -403,7 +389,6 @@ golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/internal/cmdutils/errors.go b/internal/cmdutils/errors.go index 8d65defb2e022ea63a0a41f022dbdbb7104afd6c..3c0402af28bb1432fb6af1a9e3e4082784643c5b 100644 --- a/internal/cmdutils/errors.go +++ b/internal/cmdutils/errors.go @@ -5,9 +5,10 @@ import ( "fmt" "io" - "github.com/AlecAivazis/survey/v2/terminal" "github.com/charmbracelet/fang" "github.com/spf13/cobra" + + "gitlab.com/gitlab-org/cli/internal/iostreams" ) // FlagError is the kind of error raised in flag processing @@ -56,9 +57,9 @@ func WrapError(err error, log string) *ExitError { func CancelError(log ...any) error { if len(log) < 1 { - return WrapErrorWithCode(terminal.InterruptErr, 2, "action cancelled") + return WrapErrorWithCode(iostreams.ErrUserCancelled, 2, "action cancelled") } - return WrapErrorWithCode(terminal.InterruptErr, 2, fmt.Sprint(log...)) + return WrapErrorWithCode(iostreams.ErrUserCancelled, 2, fmt.Sprint(log...)) } func (e *ExitError) Error() string { diff --git a/internal/commands/release/create/create.go b/internal/commands/release/create/create.go index 0848513920159cdc807105936c2f76e7370c18cb..44d1027d875c4c33654edb3883a8131c270cec00 100644 --- a/internal/commands/release/create/create.go +++ b/internal/commands/release/create/create.go @@ -49,8 +49,7 @@ var noteOptionsNames = map[noteOptions]string{ } type options struct { - // The following fields must be exported because of survey - // TODO: make survey independent of command options struct. + // The following fields must be exported for use with huh forms Name string ReleaseNotesAction string diff --git a/internal/surveyext/surveyext.go b/internal/surveyext/surveyext.go deleted file mode 100644 index 2d1d544e2eed12cd721e21d455b14c39a67cdafe..0000000000000000000000000000000000000000 --- a/internal/surveyext/surveyext.go +++ /dev/null @@ -1,242 +0,0 @@ -// this is a wrapper for https://github.com/AlecAivazis/survey package but with -// additional extensions and customizations for glab -// adapted from https://github.com/cli/cli/blob/trunk/pkg/surveyext/editor.go -package surveyext - -import ( - "bytes" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - - "github.com/AlecAivazis/survey/v2" - "github.com/AlecAivazis/survey/v2/terminal" - "github.com/kballard/go-shellquote" - - "gitlab.com/gitlab-org/cli/internal/execext" -) - -var ( - bom = []byte{0xef, 0xbb, 0xbf} - defaultEditor = "nano" // EXTENDED to switch from vim as a default editor - editorSkipKey = 's' - editorOpenKey = 'e' -) - -type showable interface { - Show() error -} - -func init() { - if runtime.GOOS == "windows" { - defaultEditor = "notepad" - } else if g := os.Getenv("GIT_EDITOR"); g != "" { - defaultEditor = g - } else if v := os.Getenv("VISUAL"); v != "" { - defaultEditor = v - } else if e := os.Getenv("EDITOR"); e != "" { - defaultEditor = e - } -} - -// EXTENDED to enable different prompting behavior -type GLabEditor struct { - *survey.Editor - EditorCommand string - BlankAllowed bool -} - -func (e *GLabEditor) editorCommand() string { - if e.EditorCommand == "" { - return defaultEditor - } - - return e.EditorCommand -} - -// EXTENDED to change prompt text -var EditorQuestionTemplate = ` -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}} - {{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} - {{- color "cyan"}}[(e) or Enter to launch {{ .EditorCommand }}{{- if .BlankAllowed }}, (s) or Esc to skip{{ end }}] {{color "reset"}} -{{- end}}` - -// EXTENDED to pass editor name (to use in prompt) -type EditorTemplateData struct { - survey.Editor - EditorCommand string - BlankAllowed bool - Answer string - ShowAnswer bool - ShowHelp bool - Config *survey.PromptConfig -} - -// EXTENDED to augment prompt text and keypress handling -func (e *GLabEditor) prompt(initialValue string, config *survey.PromptConfig) (any, error) { - err := e.Render( - EditorQuestionTemplate, - // EXTENDED to support printing editor in prompt and BlankAllowed - EditorTemplateData{ - Editor: *e.Editor, - BlankAllowed: e.BlankAllowed, - EditorCommand: filepath.Base(e.editorCommand()), - Config: config, - }, - ) - if err != nil { - return "", err - } - - // start reading runes from the standard in - rr := e.NewRuneReader() - _ = rr.SetTermMode() - defer func() { _ = rr.RestoreTermMode() }() - - cursor := e.NewCursor() - _ = cursor.Hide() - defer func() { - _ = cursor.Show() - }() - - for { - // EXTENDED to handle the Enter or e to edit / s or Esc to skip behavior + BlankAllowed - r, _, err := rr.ReadRune() - if err != nil { - return "", err - } - if r == editorOpenKey || r == '\r' || r == '\n' { - break - } - if r == editorSkipKey || r == terminal.KeyEscape { - if e.BlankAllowed { - return "", nil - } else { - continue - } - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - if string(r) == config.HelpInput && e.Help != "" { - err = e.Render( - EditorQuestionTemplate, - EditorTemplateData{ - // EXTENDED to support printing editor in prompt, BlankAllowed - Editor: *e.Editor, - BlankAllowed: e.BlankAllowed, - EditorCommand: filepath.Base(e.editorCommand()), - ShowHelp: true, - Config: config, - }, - ) - if err != nil { - return "", err - } - } - continue - } - - stdio := e.Stdio() - text, err := Edit(e.editorCommand(), e.FileName, initialValue, stdio.In, stdio.Out, stdio.Err, cursor) - if err != nil { - return "", err - } - - // check length, return default value on empty - if text == "" && !e.AppendDefault { - return e.Default, nil - } - - return text, nil -} - -// EXTENDED This is straight copypasta from survey to get our overridden prompt called.; -func (e *GLabEditor) Prompt(config *survey.PromptConfig) (any, error) { - initialValue := "" - if e.Default != "" && e.AppendDefault { - initialValue = e.Default - } - return e.prompt(initialValue, config) -} - -func Edit(editorCommand, fn, initialValue string, stdin io.Reader, stdout io.Writer, stderr io.Writer, cursor showable) (string, error) { - // prepare the temp file - pattern := fn - if pattern == "" { - pattern = "survey*.txt" - } - f, err := os.CreateTemp("", pattern) - if err != nil { - return "", err - } - defer os.Remove(f.Name()) - - // write utf8 BOM header - // The reason why we do this is because notepad.exe on Windows determines the - // encoding of an "empty" text file by the locale, for example, GBK in China, - // while golang string only handles utf8 well. However, a text file with utf8 - // BOM header is not considered "empty" on Windows, and the encoding will then - // be determined utf8 by notepad.exe, instead of GBK or other encodings. - if _, err := f.Write(bom); err != nil { - return "", err - } - - // write initial value - if _, err := f.WriteString(initialValue); err != nil { - return "", err - } - - // close the fd to prevent the editor unable to save file - if err := f.Close(); err != nil { - return "", err - } - - if editorCommand == "" { - editorCommand = defaultEditor - } - args, err := shellquote.Split(editorCommand) - if err != nil { - return "", err - } - args = append(args, f.Name()) - - editorExe, err := execext.LookPath(args[0]) - if err != nil { - return "", err - } - - cmd := exec.Command(editorExe, args[1:]...) - cmd.Stdin = stdin - cmd.Stdout = stdout - cmd.Stderr = stderr - - if cursor != nil { - _ = cursor.Show() - } - - // open the editor - if err := cmd.Run(); err != nil { - return "", err - } - - // raw is a BOM-unstripped UTF8 byte slice - raw, err := os.ReadFile(f.Name()) - if err != nil { - return "", err - } - - // strip BOM header - return string(bytes.TrimPrefix(raw, bom)), nil -}