editor.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package survey
  2. import (
  3. "bytes"
  4. "io/ioutil"
  5. "os"
  6. "os/exec"
  7. "runtime"
  8. "github.com/kataras/survey/core"
  9. "github.com/kataras/survey/terminal"
  10. )
  11. /*
  12. Editor launches an instance of the users preferred editor on a temporary file.
  13. The editor to use is determined by reading the $VISUAL or $EDITOR environment
  14. variables. If neither of those are present, notepad (on Windows) or vim
  15. (others) is used.
  16. The launch of the editor is triggered by the enter key. Since the response may
  17. be long, it will not be echoed as Input does, instead, it print <Received>.
  18. Response type is a string.
  19. message := ""
  20. prompt := &survey.Editor{ Message: "What is your commit message?" }
  21. survey.AskOne(prompt, &message, nil)
  22. */
  23. type Editor struct {
  24. core.Renderer
  25. Message string
  26. Default string
  27. Help string
  28. }
  29. // data available to the templates when processing
  30. type EditorTemplateData struct {
  31. Editor
  32. Answer string
  33. ShowAnswer bool
  34. ShowHelp bool
  35. }
  36. // Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
  37. var EditorQuestionTemplate = `
  38. {{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
  39. {{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
  40. {{- color "default+hb"}}{{ .Message }} {{color "reset"}}
  41. {{- if .ShowAnswer}}
  42. {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
  43. {{- else }}
  44. {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
  45. {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
  46. {{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
  47. {{- end}}`
  48. var (
  49. bom = []byte{0xef, 0xbb, 0xbf}
  50. editor = "vim"
  51. )
  52. func init() {
  53. if runtime.GOOS == "windows" {
  54. editor = "notepad"
  55. }
  56. if v := os.Getenv("VISUAL"); v != "" {
  57. editor = v
  58. } else if e := os.Getenv("EDITOR"); e != "" {
  59. editor = e
  60. }
  61. }
  62. func (e *Editor) Prompt() (interface{}, error) {
  63. // render the template
  64. err := e.Render(
  65. EditorQuestionTemplate,
  66. EditorTemplateData{Editor: *e},
  67. )
  68. if err != nil {
  69. return "", err
  70. }
  71. // start reading runes from the standard in
  72. rr := terminal.NewRuneReader(os.Stdin)
  73. rr.SetTermMode()
  74. defer rr.RestoreTermMode()
  75. terminal.CursorHide()
  76. defer terminal.CursorShow()
  77. for {
  78. r, _, err := rr.ReadRune()
  79. if err != nil {
  80. return "", err
  81. }
  82. if r == '\r' || r == '\n' {
  83. break
  84. }
  85. if r == terminal.KeyInterrupt {
  86. return "", terminal.InterruptErr
  87. }
  88. if r == terminal.KeyEndTransmission {
  89. break
  90. }
  91. if r == core.HelpInputRune && e.Help != "" {
  92. err = e.Render(
  93. EditorQuestionTemplate,
  94. EditorTemplateData{Editor: *e, ShowHelp: true},
  95. )
  96. if err != nil {
  97. return "", err
  98. }
  99. }
  100. continue
  101. }
  102. // prepare the temp file
  103. f, err := ioutil.TempFile("", "survey")
  104. if err != nil {
  105. return "", err
  106. }
  107. defer os.Remove(f.Name())
  108. // write utf8 BOM header
  109. // The reason why we do this is because notepad.exe on Windows determines the
  110. // encoding of an "empty" text file by the locale, for example, GBK in China,
  111. // while golang string only handles utf8 well. However, a text file with utf8
  112. // BOM header is not considered "empty" on Windows, and the encoding will then
  113. // be determined utf8 by notepad.exe, instead of GBK or other encodings.
  114. if _, err := f.Write(bom); err != nil {
  115. return "", err
  116. }
  117. // close the fd to prevent the editor unable to save file
  118. if err := f.Close(); err != nil {
  119. return "", err
  120. }
  121. // open the editor
  122. cmd := exec.Command(editor, f.Name())
  123. cmd.Stdin = os.Stdin
  124. cmd.Stdout = os.Stdout
  125. cmd.Stderr = os.Stderr
  126. terminal.CursorShow()
  127. if err := cmd.Run(); err != nil {
  128. return "", err
  129. }
  130. // raw is a BOM-unstripped UTF8 byte slice
  131. raw, err := ioutil.ReadFile(f.Name())
  132. if err != nil {
  133. return "", err
  134. }
  135. // strip BOM header
  136. text := string(bytes.TrimPrefix(raw, bom))
  137. // check length, return default value on empty
  138. if len(text) == 0 {
  139. return e.Default, nil
  140. }
  141. return text, nil
  142. }
  143. func (e *Editor) Cleanup(val interface{}) error {
  144. return e.Render(
  145. EditorQuestionTemplate,
  146. EditorTemplateData{Editor: *e, Answer: "<Received>", ShowAnswer: true},
  147. )
  148. }