Repository: AlecAivazis/survey
Pull request #304 (2020-10-06 11:45:47)

[AlecAivazis/survey#96] add support for tab completion on input
related to AlecAivazis/survey#96


lucassabreu: i will create a new section similar to "filtering options" on how to use the new feature. but the code i think is done
lucassabreu: thank you @AlecAivazis, i am glad you liked it.

i implemented the tab to go to next item for the suggestions now.

i also realized the select and multiselect does not have this behavior and will implement on then.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.md b/.github/ISSUE_TEMPLATE/ask-for-help.md
new file mode 100644
index 0000000..644920e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/ask-for-help.md
@@ -0,0 +1,10 @@
+---
+name: Ask for help
+about: Suggest an idea for this project or ask for help
+title: ''
+labels: 'Help Wanted'
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..326e0a4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,16 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: 'Bug'
+assignees: ''
+
+---
+
+**What operating system and terminal are you using?**
+
+**An example that showcases the bug.**
+
+**What did you expect to see?**
+
+**What did you see instead?**
diff --git a/.github/ISSUE_TEMPLATE/others-suggestions.md b/.github/ISSUE_TEMPLATE/others-suggestions.md
new file mode 100644
index 0000000..6e7d600
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/others-suggestions.md
@@ -0,0 +1,10 @@
+---
+name: Others/suggestions
+about: Suggestions and other topics
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/README.md b/README.md
index 511c414..4569ad7 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ func main() {
 1. [Running the Prompts](#running-the-prompts)
 1. [Prompts](#prompts)
    1. [Input](#input)
+      1. [Suggestion Options](#suggestion-options)
    1. [Multiline](#multiline)
    1. [Password](#password)
    1. [Confirm](#confirm)
@@ -137,6 +138,23 @@ prompt := &survey.Input{
 survey.AskOne(prompt, &name)
 ```
 
+#### Suggestion Options
+
+<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
+
+```golang
+file := ""
+prompt := &survey.Input{
+    Message: "inform a file to save:",
+    Suggest: func (toComplete string) []string {
+        files, _ := filepath.Glob(toComplete + "*")
+        return files
+    },
+}
+}
+survey.AskOne(prompt, &file)
+```
+
 ### Multiline
 
 <img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
diff --git a/examples/inputfilesuggestion.go b/examples/inputfilesuggestion.go
new file mode 100644
index 0000000..8872cc4
--- /dev/null
+++ b/examples/inputfilesuggestion.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"github.com/AlecAivazis/survey/v2"
+)
+
+func suggestFiles(toComplete string) []string {
+	files, _ := filepath.Glob(toComplete + "*")
+	return files
+}
+
+// the questions to ask
+var q = []*survey.Question{
+	{
+		Name: "file",
+		Prompt: &survey.Input{
+			Message: "Which file should be read?",
+			Suggest: suggestFiles,
+			Help:    "Any file; do not need to exist yet",
+		},
+		Validate: survey.Required,
+	},
+}
+
+func main() {
+	answers := struct {
+		File string
+	}{}
+
+	// ask the question
+	err := survey.Ask(q, &answers)
+
+	if err != nil {
+		fmt.Println(err.Error())
+		return
+	}
+	// print the answers
+	fmt.Printf("File chosen %s.\n", answers.File)
+}
diff --git a/input.go b/input.go
index 407701a..f34177f 100644
--- a/input.go
+++ b/input.go
@@ -1,5 +1,10 @@
 package survey
 
+import (
+	"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.
@@ -10,18 +15,26 @@ and accepts the input with the enter key. Response type is a string.
 */
 type Input struct {
 	Renderer
-	Message string
-	Default string
-	Help    string
+	Message       string
+	Default       string
+	Help          string
+	Suggest       func(toComplete string) []string
+	typedAnswer   string
+	answer        string
+	options       []core.OptionAnswer
+	selectedIndex int
+	showingHelp   bool
 }
 
 // data available to the templates when processing
 type InputTemplateData struct {
 	Input
-	Answer     string
-	ShowAnswer bool
-	ShowHelp   bool
-	Config     *PromptConfig
+	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
@@ -31,11 +44,92 @@ var InputQuestionTemplate = `
 {{- 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 and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ print .Config.HelpInput }} for help]{{color "reset"}} {{end}}
+  {{- 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}}
+  {{- .Answer -}}
 {{- end}}`
 
+func (i *Input) OnChange(key rune, config *PromptConfig) (bool, error) {
+	if key == terminal.KeyEnter || key == '\n' {
+		if i.answer != config.HelpInput || i.Help == "" {
+			// we're done
+			return true, nil
+		} else {
+			i.answer = ""
+			i.showingHelp = true
+		}
+	} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
+		i.answer = ""
+	} else if key == terminal.KeyEscape && i.Suggest != nil {
+		if len(i.options) > 0 {
+			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 {
+		options := i.Suggest(i.answer)
+		i.selectedIndex = 0
+		i.typedAnswer = i.answer
+		if len(options) > 0 {
+			i.answer = options[0]
+			if len(options) == 1 {
+				i.options = nil
+			} else {
+				i.options = core.OptionAnswerList(options)
+			}
+		}
+	} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
+		if i.answer != "" {
+			i.answer = i.answer[0 : len(i.answer)-1]
+		}
+	} else 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,
+		},
+	)
+
+	return err != nil, err
+}
+
 func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
 	// render the template
 	err := i.Render(
@@ -55,41 +149,39 @@ func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
 	defer rr.RestoreTermMode()
 
 	cursor := i.NewCursor()
+	cursor.Hide()       // hide the cursor
+	defer cursor.Show() // show the cursor when we're done
 
-	line := []rune{}
-	// get the next line
+	// start waiting for input
 	for {
-		line, err = rr.ReadLine(0)
+		r, _, err := rr.ReadRune()
 		if err != nil {
-			return string(line), err
+			return "", err
 		}
-		// terminal will echo the \n so we need to jump back up one row
-		cursor.Up(1)
-
-		if string(line) == config.HelpInput && i.Help != "" {
-			err = i.Render(
-				InputQuestionTemplate,
-				InputTemplateData{
-					Input:    *i,
-					ShowHelp: true,
-					Config:   config,
-				},
-			)
-			if err != nil {
-				return "", err
-			}
-			continue
+		if r == terminal.KeyInterrupt {
+			return "", terminal.InterruptErr
+		}
+		if r == terminal.KeyEndTransmission {
+			break
+		}
+
+		b, err := i.OnChange(r, config)
+		if err != nil {
+			return "", err
+		}
+
+		if b {
+			break
 		}
-		break
 	}
 
 	// if the line is empty
-	if line == nil || len(line) == 0 {
+	if len(i.answer) == 0 {
 		// use the default value
 		return i.Default, err
 	}
 
-	lineStr := string(line)
+	lineStr := i.answer
 
 	i.AppendRenderedText(lineStr)
 
@@ -102,7 +194,6 @@ func (i *Input) Cleanup(config *PromptConfig, val interface{}) error {
 		InputQuestionTemplate,
 		InputTemplateData{
 			Input:      *i,
-			Answer:     val.(string),
 			ShowAnswer: true,
 			Config:     config,
 		},
diff --git a/multiselect.go b/multiselect.go
index cf0da03..0e43477 100644
--- a/multiselect.go
+++ b/multiselect.go
@@ -77,7 +77,7 @@ func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
 			// decrement the selected index
 			m.selectedIndex--
 		}
-	} else if key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
+	} else if key == terminal.KeyTab || key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
 		// if we are at the bottom of the list
 		if m.selectedIndex == len(options)-1 {
 			// start at the top
diff --git a/select.go b/select.go
index ac369ba..bc564aa 100644
--- a/select.go
+++ b/select.go
@@ -78,7 +78,7 @@ func (s *Select) OnChange(key rune, config *PromptConfig) bool {
 		return false
 
 		// if the user pressed the up arrow or 'k' to emulate vim
-	} else if key == terminal.KeyArrowUp || (s.VimMode && key == 'k') && len(options) > 0 {
+	} else if (key == terminal.KeyArrowUp || (s.VimMode && key == 'k')) && len(options) > 0 {
 		s.useDefault = false
 
 		// if we are at the top of the list
@@ -91,7 +91,7 @@ func (s *Select) OnChange(key rune, config *PromptConfig) bool {
 		}
 
 		// if the user pressed down or 'j' to emulate vim
-	} else if key == terminal.KeyArrowDown || (s.VimMode && key == 'j') && len(options) > 0 {
+	} else if (key == terminal.KeyTab || key == terminal.KeyArrowDown || (s.VimMode && key == 'j')) && len(options) > 0 {
 		s.useDefault = false
 		// if we are at the bottom of the list
 		if s.selectedIndex == len(options)-1 {
diff --git a/survey.go b/survey.go
index ede2727..e004cf3 100644
--- a/survey.go
+++ b/survey.go
@@ -19,8 +19,9 @@ func defaultAskOptions() *AskOptions {
 			Err: os.Stderr,
 		},
 		PromptConfig: PromptConfig{
-			PageSize:  7,
-			HelpInput: "?",
+			PageSize:     7,
+			HelpInput:    "?",
+			SuggestInput: "tab",
 			Icons: IconSet{
 				Error: Icon{
 					Text:   "X",
@@ -107,11 +108,12 @@ type Question struct {
 
 // PromptConfig holds the global configuration for a prompt
 type PromptConfig struct {
-	PageSize   int
-	Icons      IconSet
-	HelpInput  string
-	Filter     func(filter string, option string, index int) bool
-	KeepFilter bool
+	PageSize     int
+	Icons        IconSet
+	HelpInput    string
+	SuggestInput string
+	Filter       func(filter string, option string, index int) bool
+	KeepFilter   bool
 }
 
 // Prompt is the primary interface for the objects that can take user input
diff --git a/terminal/sequences.go b/terminal/sequences.go
index a9158c6..6d9e877 100644
--- a/terminal/sequences.go
+++ b/terminal/sequences.go
@@ -23,6 +23,7 @@ const (
 	SpecialKeyEnd      = '\x11'
 	SpecialKeyDelete   = '\x12'
 	IgnoreKey          = '\000'
+	KeyTab             = '\t'
 )
 
 func soundBell(out io.Writer) {


Test patch:
diff --git a/editor_test.go b/editor_test.go
index b670437..97f1144 100644
--- a/editor_test.go
+++ b/editor_test.go
@@ -102,6 +102,10 @@ func TestEditorRender(t *testing.T) {
 }
 
 func TestEditorPrompt(t *testing.T) {
+	if os.Getenv("SKIP_EDITOR_PROMPT_TESTS") != "" {
+		t.Skip("editor prompt tests skipped by dev")
+	}
+
 	if _, err := exec.LookPath("vi"); err != nil {
 		t.Skip("vi not found in PATH")
 	}
diff --git a/input_test.go b/input_test.go
index 564d15b..52dce04 100644
--- a/input_test.go
+++ b/input_test.go
@@ -20,6 +20,8 @@ func init() {
 
 func TestInputRender(t *testing.T) {
 
+	suggestFn := func(string) (s []string) { return s }
+
 	tests := []struct {
 		title    string
 		prompt   Input
@@ -41,7 +43,7 @@ func TestInputRender(t *testing.T) {
 		{
 			"Test Input answer output",
 			Input{Message: "What is your favorite month:"},
-			InputTemplateData{Answer: "October", ShowAnswer: true},
+			InputTemplateData{ShowAnswer: true, Answer: "October"},
 			fmt.Sprintf("%s What is your favorite month: October\n", defaultIcons().Question.Text),
 		},
 		{
@@ -68,6 +70,47 @@ func TestInputRender(t *testing.T) {
 			InputTemplateData{ShowHelp: true},
 			fmt.Sprintf("%s This is helpful\n%s What is your favorite month: (April) ", defaultIcons().Help.Text, defaultIcons().Question.Text),
 		},
+		{
+			"Test Input question output with completion",
+			Input{Message: "What is your favorite month:", Suggest: suggestFn},
+			InputTemplateData{},
+			fmt.Sprintf("%s What is your favorite month: [%s for suggestions] ", defaultIcons().Question.Text, string(defaultPromptConfig().SuggestInput)),
+		},
+		{
+			"Test Input question output with suggestions and help hidden",
+			Input{Message: "What is your favorite month:", Suggest: suggestFn, Help: "This is helpful"},
+			InputTemplateData{},
+			fmt.Sprintf("%s What is your favorite month: [%s for help, %s for suggestions] ", defaultIcons().Question.Text, string(defaultPromptConfig().HelpInput), string(defaultPromptConfig().SuggestInput)),
+		},
+		{
+			"Test Input question output with suggestions and default and help hidden",
+			Input{Message: "What is your favorite month:", Suggest: suggestFn, Help: "This is helpful", Default: "April"},
+			InputTemplateData{},
+			fmt.Sprintf("%s What is your favorite month: [%s for help, %s for suggestions] (April) ", defaultIcons().Question.Text, string(defaultPromptConfig().HelpInput), string(defaultPromptConfig().SuggestInput)),
+		},
+		{
+			"Test Input question output with suggestions shown",
+			Input{Message: "What is your favorite month:", Suggest: suggestFn},
+			InputTemplateData{
+				Answer:        "February",
+				PageEntries:   core.OptionAnswerList([]string{"January", "February", "March", "etc..."}),
+				SelectedIndex: 1,
+			},
+			fmt.Sprintf(
+				"%s What is your favorite month: February [Use arrows to move, enter to select, type to continue]\n"+
+					"  January\n%s February\n  March\n  etc...\n",
+				defaultIcons().Question.Text, defaultPromptConfig().Icons.SelectFocus.Text,
+			),
+		},
+		{
+			"Test Input question output with suggestion complemented",
+			Input{Message: "What is your favorite month:", Suggest: suggestFn},
+			InputTemplateData{Answer: "February and"},
+			fmt.Sprintf(
+				"%s What is your favorite month: [%s for suggestions] February and",
+				defaultIcons().Question.Text, defaultPromptConfig().SuggestInput,
+			),
+		},
 	}
 
 	for _, test := range tests {
@@ -95,6 +138,7 @@ func TestInputRender(t *testing.T) {
 }
 
 func TestInputPrompt(t *testing.T) {
+
 	tests := []PromptTest{
 		{
 			"Test Input prompt interaction",
@@ -165,6 +209,146 @@ func TestInputPrompt(t *testing.T) {
 			},
 			"R",
 		},
+		{
+			"Test Input prompt interaction when ask for suggestion with empty value",
+			&Input{
+				Message: "What is your favorite month?",
+				Suggest: func(string) []string {
+					return []string{"January", "February"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("What is your favorite month?")
+				c.Send(string(terminal.KeyTab))
+				c.ExpectString("January")
+				c.ExpectString("February")
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"January",
+		},
+		{
+			"Test Input prompt interaction when ask for suggestion with some value",
+			&Input{
+				Message: "What is your favorite month?",
+				Suggest: func(string) []string {
+					return []string{"February"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("What is your favorite month?")
+				c.Send("feb")
+				c.Send(string(terminal.KeyTab))
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"February",
+		},
+		{
+			"Test Input prompt interaction when ask for suggestion with some value, choosing the second one",
+			&Input{
+				Message: "What is your favorite month?",
+				Suggest: func(string) []string {
+					return []string{"January", "February", "March"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("What is your favorite month?")
+				c.Send(string(terminal.KeyTab))
+				c.Send(string(terminal.KeyArrowDown))
+				c.Send(string(terminal.KeyArrowDown))
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"March",
+		},
+		{
+			"Test Input prompt interaction when ask for suggestion with some value, choosing the second one",
+			&Input{
+				Message: "What is your favorite month?",
+				Suggest: func(string) []string {
+					return []string{"January", "February", "March"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("What is your favorite month?")
+				c.Send(string(terminal.KeyTab))
+				c.Send(string(terminal.KeyArrowDown))
+				c.Send(string(terminal.KeyArrowDown))
+				c.Send(string(terminal.KeyArrowUp))
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"February",
+		},
+		{
+			"Test Input prompt interaction when ask for suggestion, complementing it and get new suggestions",
+			&Input{
+				Message: "Where to save it?",
+				Suggest: func(complete string) []string {
+					if complete == "" {
+						return []string{"folder1/", "folder2/", "folder3/"}
+					}
+					return []string{"folder3/file1.txt", "folder3/file2.txt"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("Where to save it?")
+				c.Send(string(terminal.KeyTab))
+				c.ExpectString("folder1/")
+				c.Send(string(terminal.KeyArrowDown))
+				c.Send(string(terminal.KeyArrowDown))
+				c.Send("f")
+				c.Send(string(terminal.KeyTab))
+				c.ExpectString("folder3/file2.txt")
+				c.Send(string(terminal.KeyArrowDown))
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"folder3/file2.txt",
+		},
+		{
+			"Test Input prompt interaction when asked suggestions, but abort suggestions",
+			&Input{
+				Message: "Wanna a suggestion?",
+				Suggest: func(string) []string {
+					return []string{"suggest1", "suggest2"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("Wanna a suggestion?")
+				c.Send("typed answer")
+				c.Send(string(terminal.KeyTab))
+				c.ExpectString("suggest1")
+				c.Send(string(terminal.KeyEscape))
+				c.ExpectString("typed answer")
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"typed answer",
+		},
+		{
+			"Test Input prompt interaction with suggestions, when tabbed with list being shown, should select next suggestion",
+			&Input{
+				Message: "Choose the special one:",
+				Suggest: func(string) []string {
+					return []string{"suggest1", "suggest2", "special answer"}
+				},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("Choose the special one:")
+				c.Send("s")
+				c.Send(string(terminal.KeyTab))
+				c.ExpectString("suggest1")
+				c.ExpectString("suggest2")
+				c.ExpectString("special answer")
+				c.Send(string(terminal.KeyTab))
+				c.Send(string(terminal.KeyTab))
+				c.SendLine("")
+				c.ExpectEOF()
+			},
+			"special answer",
+		},
 	}
 
 	for _, test := range tests {
diff --git a/multiselect_test.go b/multiselect_test.go
index 9c380c0..c2fd403 100644
--- a/multiselect_test.go
+++ b/multiselect_test.go
@@ -171,6 +171,26 @@ func TestMultiSelectPrompt(t *testing.T) {
 			},
 			[]core.OptionAnswer{core.OptionAnswer{Value: "Monday", Index: 1}},
 		},
+		{
+			"cycle to next when tab send",
+			&MultiSelect{
+				Message: "What days do you prefer:",
+				Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("What days do you prefer:  [Use arrows to move, space to select, <right> to all, <left> to none, type to filter]")
+				// Select Monday.
+				c.Send(string(terminal.KeyTab))
+				c.Send(" ")
+				c.Send(string(terminal.KeyArrowDown))
+				c.SendLine(" ")
+				c.ExpectEOF()
+			},
+			[]core.OptionAnswer{
+				core.OptionAnswer{Value: "Monday", Index: 1},
+				core.OptionAnswer{Value: "Tuesday", Index: 2},
+			},
+		},
 		{
 			"default value as []string",
 			&MultiSelect{
@@ -468,7 +488,6 @@ func TestMultiSelectPrompt(t *testing.T) {
 				core.OptionAnswer{Value: "Saturday", Index: 6},
 			},
 		},
-
 	}
 
 	for _, test := range tests {
diff --git a/select_test.go b/select_test.go
index fc3b985..c960e31 100644
--- a/select_test.go
+++ b/select_test.go
@@ -134,6 +134,22 @@ func TestSelectPrompt(t *testing.T) {
 			},
 			core.OptionAnswer{Index: 1, Value: "blue"},
 		},
+		{
+			"basic interaction",
+			&Select{
+				Message: "Choose a color:",
+				Options: []string{"red", "blue", "green"},
+			},
+			func(c *expect.Console) {
+				c.ExpectString("Choose a color:")
+				// Select blue.
+				c.Send(string(terminal.KeyArrowDown))
+				// Select green.
+				c.SendLine(string(terminal.KeyTab))
+				c.ExpectEOF()
+			},
+			core.OptionAnswer{Index: 2, Value: "green"},
+		},
 		{
 			"default value",
 			&Select{