forked from toolshed/abra
		
	
		
			
				
	
	
		
			220 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package survey
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 
 | |
| 	"github.com/AlecAivazis/survey/v2/core"
 | |
| 	"github.com/AlecAivazis/survey/v2/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),
 | |
| 		},
 | |
| 	)
 | |
| }
 |