123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package survey
- import (
- "bytes"
- "io/ioutil"
- "os"
- "os/exec"
- "runtime"
- shellquote "github.com/kballard/go-shellquote"
- "gopkg.in/AlecAivazis/survey.v1/core"
- "gopkg.in/AlecAivazis/survey.v1/terminal"
- )
- /*
- Editor launches an instance of the users preferred editor on a temporary file.
- The editor to use is determined by reading the $VISUAL or $EDITOR environment
- variables. If neither of those are present, notepad (on Windows) or vim
- (others) is used.
- The launch of the editor is triggered by the enter key. Since the response may
- be long, it will not be echoed as Input does, instead, it print <Received>.
- Response type is a string.
- message := ""
- prompt := &survey.Editor{ Message: "What is your commit message?" }
- survey.AskOne(prompt, &message, nil)
- */
- type Editor struct {
- core.Renderer
- Message string
- Default string
- Help string
- Editor string
- HideDefault bool
- AppendDefault bool
- }
- // data available to the templates when processing
- type EditorTemplateData struct {
- Editor
- Answer string
- ShowAnswer bool
- ShowHelp bool
- }
- // Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
- var EditorQuestionTemplate = `
- {{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
- {{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
- {{- color "default+hb"}}{{ .Message }} {{color "reset"}}
- {{- if .ShowAnswer}}
- {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
- {{- else }}
- {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
- {{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
- {{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
- {{- end}}`
- var (
- bom = []byte{0xef, 0xbb, 0xbf}
- editor = "vim"
- )
- func init() {
- if runtime.GOOS == "windows" {
- editor = "notepad"
- }
- if v := os.Getenv("VISUAL"); v != "" {
- editor = v
- } else if e := os.Getenv("EDITOR"); e != "" {
- editor = e
- }
- }
- func (e *Editor) Prompt() (interface{}, error) {
- // render the template
- err := e.Render(
- EditorQuestionTemplate,
- EditorTemplateData{Editor: *e},
- )
- if err != nil {
- return "", err
- }
- // start reading runes from the standard in
- rr := e.NewRuneReader()
- rr.SetTermMode()
- defer rr.RestoreTermMode()
- cursor := e.NewCursor()
- cursor.Hide()
- defer cursor.Show()
- for {
- r, _, err := rr.ReadRune()
- if err != nil {
- return "", err
- }
- if r == '\r' || r == '\n' {
- break
- }
- if r == terminal.KeyInterrupt {
- return "", terminal.InterruptErr
- }
- if r == terminal.KeyEndTransmission {
- break
- }
- if r == core.HelpInputRune && e.Help != "" {
- err = e.Render(
- EditorQuestionTemplate,
- EditorTemplateData{Editor: *e, ShowHelp: true},
- )
- if err != nil {
- return "", err
- }
- }
- continue
- }
- // prepare the temp file
- f, err := ioutil.TempFile("", "survey")
- 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 default value
- if e.Default != "" && e.AppendDefault {
- if _, err := f.WriteString(e.Default); err != nil {
- return "", err
- }
- }
- // close the fd to prevent the editor unable to save file
- if err := f.Close(); err != nil {
- return "", err
- }
- // check is input editor exist
- if e.Editor != "" {
- editor = e.Editor
- }
- stdio := e.Stdio()
- args, err := shellquote.Split(editor)
- if err != nil {
- return "", err
- }
- args = append(args, f.Name())
- // open the editor
- cmd := exec.Command(args[0], args[1:]...)
- cmd.Stdin = stdio.In
- cmd.Stdout = stdio.Out
- cmd.Stderr = stdio.Err
- cursor.Show()
- if err := cmd.Run(); err != nil {
- return "", err
- }
- // raw is a BOM-unstripped UTF8 byte slice
- raw, err := ioutil.ReadFile(f.Name())
- if err != nil {
- return "", err
- }
- // strip BOM header
- text := string(bytes.TrimPrefix(raw, bom))
- // check length, return default value on empty
- if len(text) == 0 && !e.AppendDefault {
- return e.Default, nil
- }
- return text, nil
- }
- func (e *Editor) Cleanup(val interface{}) error {
- return e.Render(
- EditorQuestionTemplate,
- EditorTemplateData{Editor: *e, Answer: "<Received>", ShowAnswer: true},
- )
- }
|