From 269b8fce297c8632983294805b02b3648cfad5b3 Mon Sep 17 00:00:00 2001 From: Caitlin Schmuck Date: Fri, 5 Aug 2022 14:31:15 +0000 Subject: [PATCH 1/3] Add a RemoveSelectAll config to multi-select --- README.md | 18 ++++++++++++++++++ multiselect.go | 4 ++-- multiselect_test.go | 33 +++++++++++++++++++++++++++++++++ survey.go | 11 +++++++++++ survey_test.go | 16 ++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac2c6fc7..ecaf5cf7 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,24 @@ All of the prompts have a `Help` field which can be defined to provide more info } ``` +## Removing the "Select All" option + +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 ` 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()) +``` + + ### Changing the input rune In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey diff --git a/multiselect.go b/multiselect.go index 3251ea1c..aaec783e 100644 --- a/multiselect.go +++ b/multiselect.go @@ -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, to all, 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 }} to all,{{end}} to none, 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}} @@ -134,7 +134,7 @@ 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 } diff --git a/multiselect_test.go b/multiselect_test.go index 41e8c2bb..686d2832 100644 --- a/multiselect_test.go +++ b/multiselect_test.go @@ -579,3 +579,36 @@ 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, 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) + }) + } +} diff --git a/survey.go b/survey.go index 95136ebe..97364201 100644 --- a/survey.go +++ b/survey.go @@ -58,6 +58,7 @@ func defaultAskOptions() *AskOptions { }, KeepFilter: false, ShowCursor: false, + RemoveSelectAll: false, }, } } @@ -118,6 +119,8 @@ type PromptConfig struct { Filter func(filter string, option string, index int) bool KeepFilter bool ShowCursor bool + RemoveSelectAll bool + } // Prompt is the primary interface for the objects that can take user input @@ -175,6 +178,14 @@ func WithKeepFilter(KeepFilter bool) AskOpt { } } +// WithRemoveSelectAll remove the select all option in Multiselect +func WithRemoveSelectAll(RemoveSelectAll bool) AskOpt { + return func(options *AskOptions) error { + options.PromptConfig.RemoveSelectAll = RemoveSelectAll + return nil + } +} + // WithValidator specifies a validator to use while prompting the user func WithValidator(v Validator) AskOpt { return func(options *AskOptions) error { diff --git a/survey_test.go b/survey_test.go index d0d6230e..54bad07f 100644 --- a/survey_test.go +++ b/survey_test.go @@ -97,6 +97,22 @@ 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 TestPagination_tooFew(t *testing.T) { // a small list of options choices := core.OptionAnswerList([]string{"choice1", "choice2", "choice3"}) From d33030ffcc39804055d57332c9d27d9372fa6150 Mon Sep 17 00:00:00 2001 From: Caitlin Schmuck Date: Tue, 23 Aug 2022 20:35:27 +0000 Subject: [PATCH 2/3] AddRemoveSelectNone config --- README.md | 17 ++++++++++++++++- multiselect.go | 4 ++-- multiselect_test.go | 33 +++++++++++++++++++++++++++++++++ survey.go | 33 +++++++++++++++++++++------------ survey_test.go | 16 ++++++++++++++++ 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ecaf5cf7..ba75b329 100644 --- a/README.md +++ b/README.md @@ -357,7 +357,7 @@ All of the prompts have a `Help` field which can be defined to provide more info } ``` -## Removing the "Select All" option +## 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 ` to all` message from the prompt), use the option `WithRemoveSelectAll`: @@ -374,6 +374,21 @@ prompt := &survey.Input{ survey.AskOne(prompt, &number, survey.WithRemoveSelectAll()) ``` +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 ` 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 diff --git a/multiselect.go b/multiselect.go index aaec783e..3eb83425 100644 --- a/multiselect.go +++ b/multiselect.go @@ -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,{{- if not .Config.RemoveSelectAll }} to all,{{end}} 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 }} to all,{{end}}{{- if not .Config.RemoveSelectNone }} 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}} @@ -141,7 +141,7 @@ func (m *MultiSelect) OnChange(key rune, config *PromptConfig) { 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 } diff --git a/multiselect_test.go b/multiselect_test.go index 686d2832..3d6b2ed1 100644 --- a/multiselect_test.go +++ b/multiselect_test.go @@ -612,3 +612,36 @@ func TestMultiSelectPromptRemoveSelectAll(t *testing.T) { }) } } + +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, 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) + }) + } +} diff --git a/survey.go b/survey.go index 97364201..6d1b3150 100644 --- a/survey.go +++ b/survey.go @@ -56,9 +56,10 @@ func defaultAskOptions() *AskOptions { // include this option if it matches return strings.Contains(strings.ToLower(value), filter) }, - KeepFilter: false, - ShowCursor: false, - RemoveSelectAll: false, + KeepFilter: false, + ShowCursor: false, + RemoveSelectAll: false, + RemoveSelectNone: false, }, } } @@ -112,15 +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 - RemoveSelectAll 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 @@ -186,6 +187,14 @@ func WithRemoveSelectAll(RemoveSelectAll bool) AskOpt { } } +// WithRemoveSelectNone remove the select none/unselect all in Multiselect +func WithRemoveSelectNone(RemoveSelectNone bool) AskOpt { + return func(options *AskOptions) error { + options.PromptConfig.RemoveSelectNone = RemoveSelectNone + return nil + } +} + // WithValidator specifies a validator to use while prompting the user func WithValidator(v Validator) AskOpt { return func(options *AskOptions) error { diff --git a/survey_test.go b/survey_test.go index 54bad07f..ff8a158b 100644 --- a/survey_test.go +++ b/survey_test.go @@ -113,6 +113,22 @@ func RunPromptTestRemoveSelectAll(t *testing.T, test PromptTest) { 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"}) From 2bf1fd0b00cffcaaee631b20fb476b85bc938c94 Mon Sep 17 00:00:00 2001 From: Caitlin Schmuck Date: Tue, 30 Aug 2022 15:45:40 +0000 Subject: [PATCH 3/3] Remove boolean from Remove methods --- survey.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/survey.go b/survey.go index 6d1b3150..3e03b697 100644 --- a/survey.go +++ b/survey.go @@ -180,17 +180,17 @@ func WithKeepFilter(KeepFilter bool) AskOpt { } // WithRemoveSelectAll remove the select all option in Multiselect -func WithRemoveSelectAll(RemoveSelectAll bool) AskOpt { +func WithRemoveSelectAll() AskOpt { return func(options *AskOptions) error { - options.PromptConfig.RemoveSelectAll = RemoveSelectAll + options.PromptConfig.RemoveSelectAll = true return nil } } // WithRemoveSelectNone remove the select none/unselect all in Multiselect -func WithRemoveSelectNone(RemoveSelectNone bool) AskOpt { +func WithRemoveSelectNone() AskOpt { return func(options *AskOptions) error { - options.PromptConfig.RemoveSelectNone = RemoveSelectNone + options.PromptConfig.RemoveSelectNone = true return nil } }