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