You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
5.5 KiB
219 lines
5.5 KiB
package survey
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"git.yaojiankang.top/hupeh/gcz/survey/core"
|
|
"git.yaojiankang.top/hupeh/gcz/survey/terminal"
|
|
)
|
|
|
|
/*
|
|
Input is a regular text input that prints each character the user types on the screen
|
|
and accepts the input with the enter key. Response type is a string.
|
|
|
|
name := ""
|
|
prompt := &survey.Input{ Message: "What is your name?" }
|
|
survey.AskOne(prompt, &name)
|
|
*/
|
|
type Input struct {
|
|
Renderer
|
|
Message string
|
|
Default string
|
|
Help string
|
|
Suggest func(toComplete string) []string
|
|
answer string
|
|
typedAnswer string
|
|
options []core.OptionAnswer
|
|
selectedIndex int
|
|
showingHelp bool
|
|
}
|
|
|
|
// data available to the templates when processing
|
|
type InputTemplateData struct {
|
|
Input
|
|
ShowAnswer bool
|
|
ShowHelp bool
|
|
Answer string
|
|
PageEntries []core.OptionAnswer
|
|
SelectedIndex int
|
|
Config *PromptConfig
|
|
}
|
|
|
|
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
|
var InputQuestionTemplate = `
|
|
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
|
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
|
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
|
{{- if .ShowAnswer}}
|
|
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
|
{{- else if .PageEntries -}}
|
|
{{- .Answer}} [Use arrows to move, enter to select, type to continue]
|
|
{{- "\n"}}
|
|
{{- range $ix, $choice := .PageEntries}}
|
|
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
|
{{- $choice.Value}}
|
|
{{- color "reset"}}{{"\n"}}
|
|
{{- end}}
|
|
{{- else }}
|
|
{{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[
|
|
{{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}}
|
|
{{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}}
|
|
]{{color "reset"}} {{end}}
|
|
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
|
{{- end}}`
|
|
|
|
func (i *Input) onRune(config *PromptConfig) terminal.OnRuneFn {
|
|
return terminal.OnRuneFn(func(key rune, line []rune) ([]rune, bool, error) {
|
|
if i.options != nil && (key == terminal.KeyEnter || key == '\n') {
|
|
return []rune(i.answer), true, nil
|
|
} else if i.options != nil && key == terminal.KeyEscape {
|
|
i.answer = i.typedAnswer
|
|
i.options = nil
|
|
} else if key == terminal.KeyArrowUp && len(i.options) > 0 {
|
|
if i.selectedIndex == 0 {
|
|
i.selectedIndex = len(i.options) - 1
|
|
} else {
|
|
i.selectedIndex--
|
|
}
|
|
i.answer = i.options[i.selectedIndex].Value
|
|
} else if (key == terminal.KeyArrowDown || key == terminal.KeyTab) && len(i.options) > 0 {
|
|
if i.selectedIndex == len(i.options)-1 {
|
|
i.selectedIndex = 0
|
|
} else {
|
|
i.selectedIndex++
|
|
}
|
|
i.answer = i.options[i.selectedIndex].Value
|
|
} else if key == terminal.KeyTab && i.Suggest != nil {
|
|
i.answer = string(line)
|
|
i.typedAnswer = i.answer
|
|
options := i.Suggest(i.answer)
|
|
i.selectedIndex = 0
|
|
if len(options) == 0 {
|
|
return line, false, nil
|
|
}
|
|
|
|
i.answer = options[0]
|
|
if len(options) == 1 {
|
|
i.typedAnswer = i.answer
|
|
i.options = nil
|
|
} else {
|
|
i.options = core.OptionAnswerList(options)
|
|
}
|
|
} else {
|
|
if i.options == nil {
|
|
return line, false, nil
|
|
}
|
|
|
|
if key >= terminal.KeySpace {
|
|
i.answer += string(key)
|
|
}
|
|
i.typedAnswer = i.answer
|
|
|
|
i.options = nil
|
|
}
|
|
|
|
pageSize := config.PageSize
|
|
opts, idx := paginate(pageSize, i.options, i.selectedIndex)
|
|
err := i.Render(
|
|
InputQuestionTemplate,
|
|
InputTemplateData{
|
|
Input: *i,
|
|
Answer: i.answer,
|
|
ShowHelp: i.showingHelp,
|
|
SelectedIndex: idx,
|
|
PageEntries: opts,
|
|
Config: config,
|
|
},
|
|
)
|
|
|
|
if err == nil {
|
|
err = errReadLineAgain
|
|
}
|
|
|
|
return []rune(i.typedAnswer), true, err
|
|
})
|
|
}
|
|
|
|
var errReadLineAgain = errors.New("read line again")
|
|
|
|
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
|
// render the template
|
|
err := i.Render(
|
|
InputQuestionTemplate,
|
|
InputTemplateData{
|
|
Input: *i,
|
|
Config: config,
|
|
ShowHelp: i.showingHelp,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// start reading runes from the standard in
|
|
rr := i.NewRuneReader()
|
|
_ = rr.SetTermMode()
|
|
defer func() {
|
|
_ = rr.RestoreTermMode()
|
|
}()
|
|
cursor := i.NewCursor()
|
|
if !config.ShowCursor {
|
|
cursor.Hide() // hide the cursor
|
|
defer cursor.Show() // show the cursor when we're done
|
|
}
|
|
|
|
var line []rune
|
|
|
|
for {
|
|
if i.options != nil {
|
|
line = []rune{}
|
|
}
|
|
|
|
line, err = rr.ReadLineWithDefault(0, line, i.onRune(config))
|
|
if err == errReadLineAgain {
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
i.answer = string(line)
|
|
// readline print an empty line, go up before we render the follow up
|
|
cursor.Up(1)
|
|
|
|
// if we ran into the help string
|
|
if i.answer == config.HelpInput && i.Help != "" {
|
|
// show the help and prompt again
|
|
i.showingHelp = true
|
|
return i.Prompt(config)
|
|
}
|
|
|
|
// if the line is empty
|
|
if len(i.answer) == 0 {
|
|
// use the default value
|
|
return i.Default, err
|
|
}
|
|
|
|
lineStr := i.answer
|
|
|
|
i.AppendRenderedText(lineStr)
|
|
|
|
// we're done
|
|
return lineStr, err
|
|
}
|
|
|
|
func (i *Input) Cleanup(config *PromptConfig, val interface{}) error {
|
|
return i.Render(
|
|
InputQuestionTemplate,
|
|
InputTemplateData{
|
|
Input: *i,
|
|
ShowAnswer: true,
|
|
Config: config,
|
|
Answer: val.(string),
|
|
},
|
|
)
|
|
}
|
|
|