1package survey 2 3import ( 4 "errors" 5 6 "github.com/AlecAivazis/survey/v2/core" 7 "github.com/AlecAivazis/survey/v2/terminal" 8) 9 10/* 11Input is a regular text input that prints each character the user types on the screen 12and accepts the input with the enter key. Response type is a string. 13 14 name := "" 15 prompt := &survey.Input{ Message: "What is your name?" } 16 survey.AskOne(prompt, &name) 17*/ 18type Input struct { 19 Renderer 20 Message string 21 Default string 22 Help string 23 Suggest func(toComplete string) []string 24 answer string 25 typedAnswer string 26 options []core.OptionAnswer 27 selectedIndex int 28 showingHelp bool 29} 30 31// data available to the templates when processing 32type InputTemplateData struct { 33 Input 34 ShowAnswer bool 35 ShowHelp bool 36 Answer string 37 PageEntries []core.OptionAnswer 38 SelectedIndex int 39 Config *PromptConfig 40} 41 42// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format 43var InputQuestionTemplate = ` 44{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} 45{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} 46{{- color "default+hb"}}{{ .Message }} {{color "reset"}} 47{{- if .ShowAnswer}} 48 {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} 49{{- else if .PageEntries -}} 50 {{- .Answer}} [Use arrows to move, enter to select, type to continue] 51 {{- "\n"}} 52 {{- range $ix, $choice := .PageEntries}} 53 {{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}} 54 {{- $choice.Value}} 55 {{- color "reset"}}{{"\n"}} 56 {{- end}} 57{{- else }} 58 {{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[ 59 {{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}} 60 {{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}} 61 ]{{color "reset"}} {{end}} 62 {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} 63{{- end}}` 64 65func (i *Input) onRune(config *PromptConfig) terminal.OnRuneFn { 66 return terminal.OnRuneFn(func(key rune, line []rune) ([]rune, bool, error) { 67 if i.options != nil && (key == terminal.KeyEnter || key == '\n') { 68 return []rune(i.answer), true, nil 69 } else if i.options != nil && key == terminal.KeyEscape { 70 i.answer = i.typedAnswer 71 i.options = nil 72 } else if key == terminal.KeyArrowUp && len(i.options) > 0 { 73 if i.selectedIndex == 0 { 74 i.selectedIndex = len(i.options) - 1 75 } else { 76 i.selectedIndex-- 77 } 78 i.answer = i.options[i.selectedIndex].Value 79 } else if (key == terminal.KeyArrowDown || key == terminal.KeyTab) && len(i.options) > 0 { 80 if i.selectedIndex == len(i.options)-1 { 81 i.selectedIndex = 0 82 } else { 83 i.selectedIndex++ 84 } 85 i.answer = i.options[i.selectedIndex].Value 86 } else if key == terminal.KeyTab && i.Suggest != nil { 87 i.answer = string(line) 88 i.typedAnswer = i.answer 89 options := i.Suggest(i.answer) 90 i.selectedIndex = 0 91 if len(options) == 0 { 92 return line, false, nil 93 } 94 95 i.answer = options[0] 96 if len(options) == 1 { 97 i.typedAnswer = i.answer 98 i.options = nil 99 } else { 100 i.options = core.OptionAnswerList(options) 101 } 102 } else { 103 if i.options == nil { 104 return line, false, nil 105 } 106 107 if key >= terminal.KeySpace { 108 i.answer += string(key) 109 } 110 i.typedAnswer = i.answer 111 112 i.options = nil 113 } 114 115 pageSize := config.PageSize 116 opts, idx := paginate(pageSize, i.options, i.selectedIndex) 117 err := i.Render( 118 InputQuestionTemplate, 119 InputTemplateData{ 120 Input: *i, 121 Answer: i.answer, 122 ShowHelp: i.showingHelp, 123 SelectedIndex: idx, 124 PageEntries: opts, 125 Config: config, 126 }, 127 ) 128 129 if err == nil { 130 err = readLineAgain 131 } 132 133 return []rune(i.typedAnswer), true, err 134 }) 135} 136 137var readLineAgain = errors.New("read line again") 138 139func (i *Input) Prompt(config *PromptConfig) (interface{}, error) { 140 // render the template 141 err := i.Render( 142 InputQuestionTemplate, 143 InputTemplateData{ 144 Input: *i, 145 Config: config, 146 ShowHelp: i.showingHelp, 147 }, 148 ) 149 if err != nil { 150 return "", err 151 } 152 153 // start reading runes from the standard in 154 rr := i.NewRuneReader() 155 rr.SetTermMode() 156 defer rr.RestoreTermMode() 157 158 cursor := i.NewCursor() 159 if !config.ShowCursor { 160 cursor.Hide() // hide the cursor 161 defer cursor.Show() // show the cursor when we're done 162 } 163 164 var line []rune 165 166 for { 167 if i.options != nil { 168 line = []rune{} 169 } 170 171 line, err = rr.ReadLineWithDefault(0, line, i.onRune(config)) 172 if err == readLineAgain { 173 continue 174 } 175 176 if err != nil { 177 return "", err 178 } 179 180 break 181 } 182 183 i.answer = string(line) 184 // readline print an empty line, go up before we render the follow up 185 cursor.Up(1) 186 187 // if we ran into the help string 188 if i.answer == config.HelpInput && i.Help != "" { 189 // show the help and prompt again 190 i.showingHelp = true 191 return i.Prompt(config) 192 } 193 194 // if the line is empty 195 if len(i.answer) == 0 { 196 // use the default value 197 return i.Default, err 198 } 199 200 lineStr := i.answer 201 202 i.AppendRenderedText(lineStr) 203 204 // we're done 205 return lineStr, err 206} 207 208func (i *Input) Cleanup(config *PromptConfig, val interface{}) error { 209 // use the default answer when cleaning up the prompt if necessary 210 ans := i.answer 211 if ans == "" && i.Default != "" { 212 ans = i.Default 213 } 214 215 // render the cleanup 216 return i.Render( 217 InputQuestionTemplate, 218 InputTemplateData{ 219 Input: *i, 220 ShowAnswer: true, 221 Config: config, 222 Answer: ans, 223 }, 224 ) 225} 226