Skip to content

Commit

Permalink
Added WithPageSize AskOpt (AlecAivazis#214)
Browse files Browse the repository at this point in the history
* added PromptConfig to Prompt interface

* documented WithPageSize

* selects will pull page size from global config

* removed unused variable

* fixed display problem on key press
  • Loading branch information
AlecAivazis authored Jun 3, 2019
1 parent 95d9ae9 commit 5317b92
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 42 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,18 @@ The user can also press `esc` to toggle the ability cycle through the options wi

By default, the select prompt is limited to showing 7 options at a time
and will paginate lists of options longer than that. To increase, you can either
change the global `survey.PageSize`, or set the `PageSize` field on the prompt:
set the `PageSize` field on the prompt:

```golang
prompt := &survey.Select{..., PageSize: 10}
```

Or pass an an `AskOpt` to `survey.Ask` or `survey.AskOne`:

```golang
survey.AskOne(prompt, &days, survey.WithPageSize(10))
```

### MultiSelect

<img src="https://thumbs.gfycat.com/SharpTameAntelope-size_restricted.gif" width="450px"/>
Expand All @@ -182,12 +188,18 @@ The user can also press `esc` to toggle the ability cycle through the options wi

By default, the MultiSelect prompt is limited to showing 7 options at a time
and will paginate lists of options longer than that. To increase, you can either
change the global `survey.PageSize`, or set the `PageSize` field on the prompt:
set the `PageSize` field on the prompt:

```golang
prompt := &survey.MultiSelect{..., PageSize: 10}
```

Or pass an an `AskOpt` to `survey.Ask` or `survey.AskOne`:

```golang
survey.AskOne(prompt, &days, survey.WithPageSize(10))
```

### Editor

Launches the user's preferred editor (defined by the \$EDITOR environment variable) on a
Expand Down
2 changes: 1 addition & 1 deletion confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ by a carriage return.
prompt := &survey.Confirm{ Message: "What is your name?" }
survey.AskOne(prompt, &likesPie)
*/
func (c *Confirm) Prompt() (interface{}, error) {
func (c *Confirm) Prompt(config *PromptConfig) (interface{}, error) {
// render the question template
err := c.Render(
ConfirmQuestionTemplate,
Expand Down
2 changes: 1 addition & 1 deletion editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (e *Editor) PromptAgain(invalid interface{}, err error) (interface{}, error
return e.prompt(initialValue)
}

func (e *Editor) Prompt() (interface{}, error) {
func (e *Editor) Prompt(config *PromptConfig) (interface{}, error) {
initialValue := ""
if e.Default != "" && e.AppendDefault {
initialValue = e.Default
Expand Down
2 changes: 1 addition & 1 deletion input.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var InputQuestionTemplate = `
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
{{- end}}`

func (i *Input) Prompt() (interface{}, error) {
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
// render the template
err := i.Render(
InputQuestionTemplate,
Expand Down
2 changes: 1 addition & 1 deletion multiline.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var MultilineQuestionTemplate = `
{{- color "cyan"}}[Enter 2 empty lines to finish]{{color "reset"}}
{{- end}}`

func (i *Multiline) Prompt() (interface{}, error) {
func (i *Multiline) Prompt(config *PromptConfig) (interface{}, error) {
// render the template
err := i.Render(
MultilineQuestionTemplate,
Expand Down
27 changes: 19 additions & 8 deletions multiselect.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var MultiSelectQuestionTemplate = `
{{- end}}`

// OnChange is called on every keypress.
func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
options := m.filterOptions()
oldFilter := m.filter

Expand Down Expand Up @@ -125,10 +125,17 @@ func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune,
}
}
// paginate the options
// figure out the page size
pageSize := m.PageSize
// if we dont have a specific one
if pageSize == 0 {
// grab the global value
pageSize = config.PageSize
}

// TODO if we have started filtering and were looking at the end of a list
// and we have modified the filter then we should move the page back!
opts, idx := paginate(m.PageSize, options, m.selectedIndex)
opts, idx := paginate(pageSize, options, m.selectedIndex)

// render the options
m.Render(
Expand All @@ -141,9 +148,6 @@ func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune,
PageEntries: opts,
},
)

// if we are not pressing ent
return line, 0, true
}

func (m *MultiSelect) filterOptions() []string {
Expand All @@ -156,7 +160,7 @@ func (m *MultiSelect) filterOptions() []string {
return DefaultFilter(m.filter, m.Options)
}

func (m *MultiSelect) Prompt() (interface{}, error) {
func (m *MultiSelect) Prompt(config *PromptConfig) (interface{}, error) {
// compute the default state
m.checked = make(map[string]bool)
// if there is a default
Expand All @@ -180,8 +184,15 @@ func (m *MultiSelect) Prompt() (interface{}, error) {
return "", errors.New("please provide options to select from")
}

// figure out the page size
pageSize := m.PageSize
// if we dont have a specific one
if pageSize == 0 {
// grab the global value
pageSize = config.PageSize
}
// paginate the options
opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex)
opts, idx := paginate(pageSize, m.Options, m.selectedIndex)

cursor := m.NewCursor()
cursor.Hide() // hide the cursor
Expand Down Expand Up @@ -217,7 +228,7 @@ func (m *MultiSelect) Prompt() (interface{}, error) {
if r == terminal.KeyEndTransmission {
break
}
m.OnChange(nil, 0, r)
m.OnChange(r, config)
}
m.filter = ""
m.FilterMessage = ""
Expand Down
2 changes: 1 addition & 1 deletion password.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var PasswordQuestionTemplate = `
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}`

func (p *Password) Prompt() (line interface{}, err error) {
func (p *Password) Prompt(config *PromptConfig) (line interface{}, err error) {
// render the question template
out, err := core.RunTemplate(
PasswordQuestionTemplate,
Expand Down
25 changes: 20 additions & 5 deletions select.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ var SelectQuestionTemplate = `
{{- end}}`

// OnChange is called on every keypress.
func (s *Select) OnChange(key rune) bool {
func (s *Select) OnChange(key rune, config *PromptConfig) bool {
options := s.filterOptions()
oldFilter := s.filter

Expand Down Expand Up @@ -138,10 +138,17 @@ func (s *Select) OnChange(key rune) bool {
}

// figure out the options and index to render
// figure out the page size
pageSize := s.PageSize
// if we dont have a specific one
if pageSize == 0 {
// grab the global value
pageSize = config.PageSize
}

// TODO if we have started filtering and were looking at the end of a list
// and we have modified the filter then we should move the page back!
opts, idx := paginate(s.PageSize, options, s.selectedIndex)
opts, idx := paginate(pageSize, options, s.selectedIndex)

// render the options
s.Render(
Expand All @@ -168,7 +175,7 @@ func (s *Select) filterOptions() []string {
return DefaultFilter(s.filter, s.Options)
}

func (s *Select) Prompt() (interface{}, error) {
func (s *Select) Prompt(config *PromptConfig) (interface{}, error) {
// if there are no options to render
if len(s.Options) == 0 {
// we failed
Expand All @@ -193,8 +200,16 @@ func (s *Select) Prompt() (interface{}, error) {
// save the selected index
s.selectedIndex = sel

// figure out the page size
pageSize := s.PageSize
// if we dont have a specific one
if pageSize == 0 {
// grab the global value
pageSize = config.PageSize
}

// figure out the options and index to render
opts, idx := paginate(s.PageSize, s.Options, sel)
opts, idx := paginate(pageSize, s.Options, sel)

// ask the question
err := s.Render(
Expand Down Expand Up @@ -232,7 +247,7 @@ func (s *Select) Prompt() (interface{}, error) {
if r == terminal.KeyEndTransmission {
break
}
if s.OnChange(r) {
if s.OnChange(r, config) {
break
}
}
Expand Down
47 changes: 26 additions & 21 deletions survey.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (
"github.com/AlecAivazis/survey/v2/terminal"
)

// PageSize is the default maximum number of items to show in select/multiselect prompts
var PageSize = 7

// DefaultAskOptions is the default options on ask, using the OS stdio.
var DefaultAskOptions = AskOptions{
Stdio: terminal.Stdio{
In: os.Stdin,
Out: os.Stdout,
Err: os.Stderr,
},
PromptConfig: PromptConfig{
PageSize: 7,
},
}

// Validator is a function passed to a Question after a user has provided a response.
Expand All @@ -41,10 +41,15 @@ type Question struct {
Transform Transformer
}

// PromptConfig holds the global configuration for a prompt
type PromptConfig struct {
PageSize int
}

// Prompt is the primary interface for the objects that can take user input
// and return a response.
type Prompt interface {
Prompt() (interface{}, error)
Prompt(config *PromptConfig) (interface{}, error)
Cleanup(interface{}) error
Error(error) error
}
Expand All @@ -59,8 +64,9 @@ type AskOpt func(options *AskOptions) error

// AskOptions provides additional options on ask.
type AskOptions struct {
Stdio terminal.Stdio
Validators []Validator
Stdio terminal.Stdio
Validators []Validator
PromptConfig PromptConfig
}

// WithStdio specifies the standard input, output and error files survey
Expand Down Expand Up @@ -89,6 +95,17 @@ type wantsStdio interface {
WithStdio(terminal.Stdio)
}

// WithPageSize sets the default page size used by prompts
func WithPageSize(pageSize int) AskOpt {
return func(options *AskOptions) error {
// set the page size
options.PromptConfig.PageSize = pageSize

// nothing went wrong
return nil
}
}

/*
AskOne performs the prompt for a single prompt and asks for validation if required.
Response types should be something that can be casted from the response type designated
Expand Down Expand Up @@ -157,7 +174,7 @@ func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {
}

// grab the user input and save it
ans, err := q.Prompt.Prompt()
ans, err := q.Prompt.Prompt(&options.PromptConfig)
// if there was a problem
if err != nil {
return err
Expand Down Expand Up @@ -189,7 +206,7 @@ func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {
if promptAgainer, ok := q.Prompt.(PromptAgainer); ok {
ans, err = promptAgainer.PromptAgain(ans, invalid)
} else {
ans, err = q.Prompt.Prompt()
ans, err = q.Prompt.Prompt(&options.PromptConfig)
}
// if there was a problem
if err != nil {
Expand Down Expand Up @@ -231,19 +248,7 @@ func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {

// paginate returns a single page of choices given the page size, the total list of
// possible choices, and the current selected index in the total list.
func paginate(page int, choices []string, sel int) ([]string, int) {
// the number of elements to show in a single page
var pageSize int
// if the select has a specific page size
if page != 0 {
// use the specified one
pageSize = page
// otherwise the select does not have a page size
} else {
// use the package default
pageSize = PageSize
}

func paginate(pageSize int, choices []string, sel int) ([]string, int) {
var start, end, cursor int

if len(choices) < pageSize {
Expand Down
2 changes: 1 addition & 1 deletion survey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func RunPromptTest(t *testing.T, test PromptTest) {
if p, ok := test.prompt.(wantsStdio); ok {
p.WithStdio(stdio)
}
answer, err = test.prompt.Prompt()
answer, err = test.prompt.Prompt(&PromptConfig{})
return err
})
require.Equal(t, test.expected, answer)
Expand Down

0 comments on commit 5317b92

Please sign in to comment.