select.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package survey
  2. import (
  3. "errors"
  4. "os"
  5. "github.com/kataras/survey/core"
  6. "github.com/kataras/survey/terminal"
  7. )
  8. /*
  9. Select is a prompt that presents a list of various options to the user
  10. for them to select using the arrow keys and enter. Response type is a string.
  11. color := ""
  12. prompt := &survey.Select{
  13. Message: "Choose a color:",
  14. Options: []string{"red", "blue", "green"},
  15. }
  16. survey.AskOne(prompt, &color, nil)
  17. */
  18. type Select struct {
  19. core.Renderer
  20. Message string
  21. Options []string
  22. Default string
  23. Help string
  24. PageSize int
  25. selectedIndex int
  26. useDefault bool
  27. showingHelp bool
  28. }
  29. // the data available to the templates when processing
  30. type SelectTemplateData struct {
  31. Select
  32. PageEntries []string
  33. SelectedIndex int
  34. Answer string
  35. ShowAnswer bool
  36. ShowHelp bool
  37. }
  38. var SelectQuestionTemplate = `
  39. {{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
  40. {{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
  41. {{- color "default+hb"}}{{ .Message }}{{color "reset"}}
  42. {{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
  43. {{- else}}
  44. {{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}}
  45. {{- "\n"}}
  46. {{- range $ix, $choice := .PageEntries}}
  47. {{- if eq $ix $.SelectedIndex}}{{color "cyan+b"}}{{ SelectFocusIcon }} {{else}}{{color "default+hb"}} {{end}}
  48. {{- $choice}}
  49. {{- color "reset"}}{{"\n"}}
  50. {{- end}}
  51. {{- end}}`
  52. // OnChange is called on every keypress.
  53. func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
  54. // if the user pressed the enter key
  55. if key == terminal.KeyEnter {
  56. return []rune(s.Options[s.selectedIndex]), 0, true
  57. // if the user pressed the up arrow
  58. } else if key == terminal.KeyArrowUp {
  59. s.useDefault = false
  60. // if we are at the top of the list
  61. if s.selectedIndex == 0 {
  62. // start from the button
  63. s.selectedIndex = len(s.Options) - 1
  64. } else {
  65. // otherwise we are not at the top of the list so decrement the selected index
  66. s.selectedIndex--
  67. }
  68. // if the user pressed down and there is room to move
  69. } else if key == terminal.KeyArrowDown {
  70. s.useDefault = false
  71. // if we are at the bottom of the list
  72. if s.selectedIndex == len(s.Options)-1 {
  73. // start from the top
  74. s.selectedIndex = 0
  75. } else {
  76. // increment the selected index
  77. s.selectedIndex++
  78. }
  79. // only show the help message if we have one
  80. } else if key == core.HelpInputRune && s.Help != "" {
  81. s.showingHelp = true
  82. }
  83. // figure out the options and index to render
  84. opts, idx := paginate(s.PageSize, s.Options, s.selectedIndex)
  85. // render the options
  86. s.Render(
  87. SelectQuestionTemplate,
  88. SelectTemplateData{
  89. Select: *s,
  90. SelectedIndex: idx,
  91. ShowHelp: s.showingHelp,
  92. PageEntries: opts,
  93. },
  94. )
  95. // if we are not pressing ent
  96. return []rune(s.Options[s.selectedIndex]), 0, true
  97. }
  98. func (s *Select) Prompt() (interface{}, error) {
  99. // if there are no options to render
  100. if len(s.Options) == 0 {
  101. // we failed
  102. return "", errors.New("please provide options to select from")
  103. }
  104. // start off with the first option selected
  105. sel := 0
  106. // if there is a default
  107. if s.Default != "" {
  108. // find the choice
  109. for i, opt := range s.Options {
  110. // if the option correponds to the default
  111. if opt == s.Default {
  112. // we found our initial value
  113. sel = i
  114. // stop looking
  115. break
  116. }
  117. }
  118. }
  119. // save the selected index
  120. s.selectedIndex = sel
  121. // figure out the options and index to render
  122. opts, idx := paginate(s.PageSize, s.Options, sel)
  123. // ask the question
  124. err := s.Render(
  125. SelectQuestionTemplate,
  126. SelectTemplateData{
  127. Select: *s,
  128. PageEntries: opts,
  129. SelectedIndex: idx,
  130. },
  131. )
  132. if err != nil {
  133. return "", err
  134. }
  135. // hide the cursor
  136. terminal.CursorHide()
  137. // show the cursor when we're done
  138. defer terminal.CursorShow()
  139. // by default, use the default value
  140. s.useDefault = true
  141. rr := terminal.NewRuneReader(os.Stdin)
  142. rr.SetTermMode()
  143. defer rr.RestoreTermMode()
  144. // start waiting for input
  145. for {
  146. r, _, err := rr.ReadRune()
  147. if err != nil {
  148. return "", err
  149. }
  150. if r == '\r' || r == '\n' {
  151. break
  152. }
  153. if r == terminal.KeyInterrupt {
  154. return "", terminal.InterruptErr
  155. }
  156. if r == terminal.KeyEndTransmission {
  157. break
  158. }
  159. s.OnChange(nil, 0, r)
  160. }
  161. var val string
  162. // if we are supposed to use the default value
  163. if s.useDefault {
  164. // if there is a default value
  165. if s.Default != "" {
  166. // use the default value
  167. val = s.Default
  168. } else {
  169. // there is no default value so use the first
  170. val = s.Options[0]
  171. }
  172. // otherwise the selected index points to the value
  173. } else {
  174. // the
  175. val = s.Options[s.selectedIndex]
  176. }
  177. return val, err
  178. }
  179. func (s *Select) Cleanup(val interface{}) error {
  180. return s.Render(
  181. SelectQuestionTemplate,
  182. SelectTemplateData{
  183. Select: *s,
  184. Answer: val.(string),
  185. ShowAnswer: true,
  186. },
  187. )
  188. }