Skip to content
This repository has been archived by the owner on Apr 19, 2024. It is now read-only.

Add a RemoveSelectAll and RemoveSelectNone config to multi-select #439

Merged
merged 3 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,39 @@ All of the prompts have a `Help` field which can be defined to provide more info
}
```

## Removing the "Select All" and "Select None" options

By default, users can select all of the multi-select options using the right arrow key. To prevent users from being able to do this (and remove the `<right> to all` message from the prompt), use the option `WithRemoveSelectAll`:

```golang
import (
"github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
Message: "This question has the select all option removed",
}

survey.AskOne(prompt, &number, survey.WithRemoveSelectAll())
AlecAivazis marked this conversation as resolved.
Show resolved Hide resolved
```

Also by default, users can use the left arrow key to unselect all of the options. To prevent users from being able to do this (and remove the `<left> to none` message from the prompt), use the option `WithRemoveSelectNone`:

```golang
import (
"github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
Message: "This question has the select all option removed",
}

survey.AskOne(prompt, &number, survey.WithRemoveSelectNone())
```


### Changing the input rune

In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
Expand Down
6 changes: 3 additions & 3 deletions multiselect.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ var MultiSelectQuestionTemplate = `
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
{{- else }}
{{- " "}}{{- color "cyan"}}[Use arrows to move, space to select, <right> to all, <left> to none, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
{{- " "}}{{- color "cyan"}}[Use arrows to move, space to select,{{- if not .Config.RemoveSelectAll }} <right> to all,{{end}}{{- if not .Config.RemoveSelectNone }} <left> to none,{{end}} type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
{{- "\n"}}
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
Expand Down Expand Up @@ -134,14 +134,14 @@ func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
} else if key >= terminal.KeySpace {
m.filter += string(key)
m.VimMode = false
} else if key == terminal.KeyArrowRight {
} else if !config.RemoveSelectAll && key == terminal.KeyArrowRight {
for _, v := range options {
m.checked[v.Index] = true
}
if !config.KeepFilter {
m.filter = ""
}
} else if key == terminal.KeyArrowLeft {
} else if !config.RemoveSelectNone && key == terminal.KeyArrowLeft {
for _, v := range options {
m.checked[v.Index] = false
}
Expand Down
66 changes: 66 additions & 0 deletions multiselect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,69 @@ func TestMultiSelectPromptKeepFilter(t *testing.T) {
})
}
}

func TestMultiSelectPromptRemoveSelectAll(t *testing.T) {
tests := []PromptTest{
{
"multi select with remove select all option",
&MultiSelect{
Message: "What color do you prefer:",
Options: []string{"green", "red", "light-green", "blue", "black", "yellow", "purple"},
},
func(c expectConsole) {
c.ExpectString("What color do you prefer: [Use arrows to move, space to select, <left> to none, type to filter]")
// Select the first option "green"
c.Send(" ")

// attempt to select all (this shouldn't do anything)
c.Send(string(terminal.KeyArrowRight))

// end the session
c.SendLine("")
c.ExpectEOF()
},
[]core.OptionAnswer{ // we should only have one option selected, not all of them
{Value: "green", Index: 0},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
RunPromptTestRemoveSelectAll(t, test)
})
}
}

func TestMultiSelectPromptRemoveSelectNone(t *testing.T) {
tests := []PromptTest{
{
"multi select with remove select none option",
&MultiSelect{
Message: "What color do you prefer:",
Options: []string{"green", "red", "light-green", "blue", "black", "yellow", "purple"},
},
func(c expectConsole) {
c.ExpectString("What color do you prefer: [Use arrows to move, space to select, <right> to all, type to filter]")
// Select the first option "green"
c.Send(" ")

// attempt to unselect all (this shouldn't do anything)
c.Send(string(terminal.KeyArrowLeft))

// end the session
c.SendLine("")
c.ExpectEOF()
},
[]core.OptionAnswer{ // we should only have one option selected, not all of them
{Value: "green", Index: 0},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
RunPromptTestRemoveSelectNone(t, test)
})
}
}
38 changes: 29 additions & 9 deletions survey.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ func defaultAskOptions() *AskOptions {
// include this option if it matches
return strings.Contains(strings.ToLower(value), filter)
},
KeepFilter: false,
ShowCursor: false,
KeepFilter: false,
ShowCursor: false,
RemoveSelectAll: false,
RemoveSelectNone: false,
},
}
}
Expand Down Expand Up @@ -111,13 +113,15 @@ type Question struct {

// PromptConfig holds the global configuration for a prompt
type PromptConfig struct {
PageSize int
Icons IconSet
HelpInput string
SuggestInput string
Filter func(filter string, option string, index int) bool
KeepFilter bool
ShowCursor bool
PageSize int
Icons IconSet
HelpInput string
SuggestInput string
Filter func(filter string, option string, index int) bool
KeepFilter bool
ShowCursor bool
RemoveSelectAll bool
RemoveSelectNone bool
}

// Prompt is the primary interface for the objects that can take user input
Expand Down Expand Up @@ -175,6 +179,22 @@ func WithKeepFilter(KeepFilter bool) AskOpt {
}
}

// WithRemoveSelectAll remove the select all option in Multiselect
func WithRemoveSelectAll() AskOpt {
return func(options *AskOptions) error {
options.PromptConfig.RemoveSelectAll = true
return nil
}
}

// WithRemoveSelectNone remove the select none/unselect all in Multiselect
func WithRemoveSelectNone() AskOpt {
return func(options *AskOptions) error {
options.PromptConfig.RemoveSelectNone = true
return nil
}
}

// WithValidator specifies a validator to use while prompting the user
func WithValidator(v Validator) AskOpt {
return func(options *AskOptions) error {
Expand Down
32 changes: 32 additions & 0 deletions survey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ func RunPromptTestKeepFilter(t *testing.T, test PromptTest) {
require.Equal(t, test.expected, answer)
}

func RunPromptTestRemoveSelectAll(t *testing.T, test PromptTest) {
t.Helper()
var answer interface{}
RunTest(t, test.procedure, func(stdio terminal.Stdio) error {
var err error
if p, ok := test.prompt.(wantsStdio); ok {
p.WithStdio(stdio)
}
config := defaultPromptConfig()
config.RemoveSelectAll = true
answer, err = test.prompt.Prompt(config)
return err
})
require.Equal(t, test.expected, answer)
}

func RunPromptTestRemoveSelectNone(t *testing.T, test PromptTest) {
t.Helper()
var answer interface{}
RunTest(t, test.procedure, func(stdio terminal.Stdio) error {
var err error
if p, ok := test.prompt.(wantsStdio); ok {
p.WithStdio(stdio)
}
config := defaultPromptConfig()
config.RemoveSelectNone = true
answer, err = test.prompt.Prompt(config)
return err
})
require.Equal(t, test.expected, answer)
}

func TestPagination_tooFew(t *testing.T) {
// a small list of options
choices := core.OptionAnswerList([]string{"choice1", "choice2", "choice3"})
Expand Down