From 9f89d9dd66613216993dc300f9f3dbae9c3c9bda Mon Sep 17 00:00:00 2001 From: Alec Aivazis Date: Tue, 27 Feb 2018 07:50:51 -0800 Subject: [PATCH] Added vim-like keybindings for navigating Select and MultiSelect (#124) * j and k can be used to navigate select and multi select * added test case for vim navigation * added docs for j/k movement * added j/k test to MultiSelect --- README.md | 56 ++++++++++++++++++++++---------------------- multiselect.go | 4 ++-- select.go | 8 +++---- tests/multiselect.go | 7 ++++++ tests/select.go | 6 +++++ 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index b0b24cd6..6f8ae20d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Survey + [![Build Status](https://travis-ci.org/AlecAivazis/survey.svg?branch=feature%2Fpretty)](https://travis-ci.org/AlecAivazis/survey) [![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg)](https://godoc.org/gopkg.in/AlecAivazis/survey.v1) @@ -59,16 +60,16 @@ func main() { 1. [Examples](#examples) 1. [Prompts](#prompts) - 1. [Input](#input) - 1. [Password](#password) - 1. [Confirm](#confirm) - 1. [Select](#select) - 1. [MultiSelect](#multiselect) - 1. [Editor](#editor) + 1. [Input](#input) + 1. [Password](#password) + 1. [Confirm](#confirm) + 1. [Select](#select) + 1. [MultiSelect](#multiselect) + 1. [Editor](#editor) 1. [Validation](#validation) - 1. [Built-in Validators](#built-in-validators) + 1. [Built-in Validators](#built-in-validators) 1. [Help Text](#help-text) - 1. [Changing the input rune](#changing-the-input-run) + 1. [Changing the input rune](#changing-the-input-run) 1. [Custom Types](#custom-types) 1. [Customizing Output](#customizing-output) 1. [Versioning](#versioning) @@ -101,7 +102,6 @@ prompt := &survey.Input{ survey.AskOne(prompt, &name, nil) ``` - ### Password @@ -114,7 +114,6 @@ prompt := &survey.Password{ survey.AskOne(prompt, &password, nil) ``` - ### Confirm @@ -127,7 +126,6 @@ prompt := &survey.Confirm{ survey.AskOne(prompt, &name, nil) ``` - ### Select @@ -141,6 +139,8 @@ prompt := &survey.Select{ survey.AskOne(prompt, &color, nil) ``` +The user can cycle through the options with the j and k keys to do down and up respectively. + 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: @@ -162,6 +162,8 @@ prompt := &survey.MultiSelect{ survey.AskOne(prompt, &days, nil) ``` +The user can cycle through the options with the j and k keys to do down and up, respectively. + 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: @@ -172,11 +174,10 @@ prompt := &survey.MultiSelect{..., PageSize: 10} ### Editor -Launches the user's preferred editor (defined by the $EDITOR environment variable) on a -temporary file. Once the user exits their editor, the contents of the temporary file are read in as +Launches the user's preferred editor (defined by the $EDITOR environment variable) on a +temporary file. Once the user exits their editor, the contents of the temporary file are read in as the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used. - ## Validation Validating individual responses for a particular question can be done by defining a @@ -201,11 +202,11 @@ q := &survey.Question{ `survey` comes prepackaged with a few validators to fit common situations. Currently these validators include: -| name | valid types | description | notes | -|--------------|-----------------|---------------------------------------------------------------|--------------------| -| Required | any | Rejects zero values of the response type | Boolean values pass straight through since the zero value (false) is a valid response | -| MinLength(n) | string | Enforces that a response is at least the given length | | -| MaxLength(n) | string | Enforces that a response is no longer than the given length | | +| name | valid types | description | notes | +| ------------ | ----------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| Required | any | Rejects zero values of the response type | Boolean values pass straight through since the zero value (false) is a valid response | +| MinLength(n) | string | Enforces that a response is at least the given length | | +| MaxLength(n) | string | Enforces that a response is no longer than the given length | | ## Help Text @@ -226,7 +227,6 @@ In some situations, `?` is a perfectly valid response. To handle this, you can c looks for by setting the `HelpInputRune` variable in `survey/core`: ```golang - import ( "gopkg.in/AlecAivazis/survey.v1" surveyCore "gopkg.in/AlecAivazis/survey.v1/core" @@ -278,14 +278,14 @@ survey.AskOne( Customizing the icons and various parts of survey can easily be done by setting the following variables in `survey/core`: -| name | default | description | -|---------------------|----------------|-------------------------------------------------------------------| -| ErrorIcon | ✘ | Before an error | -| HelpIcon | ⓘ | Before help text | -| QuestionIcon | ? | Before the message of a prompt | -| SelectFocusIcon | ❯ | Marks the current focus in `Select` and `MultiSelect` prompts | -| MarkedOptionIcon | ◉ | Marks a chosen selection in a `MultiSelect` prompt | -| UnmarkedOptionIcon | ◯ | Marks an unselected option in a `MultiSelect` prompt | +| name | default | description | +| ------------------ | ------- | ------------------------------------------------------------- | +| ErrorIcon | ✘ | Before an error | +| HelpIcon | ⓘ | Before help text | +| QuestionIcon | ? | Before the message of a prompt | +| SelectFocusIcon | ❯ | Marks the current focus in `Select` and `MultiSelect` prompts | +| MarkedOptionIcon | ◉ | Marks a chosen selection in a `MultiSelect` prompt | +| UnmarkedOptionIcon | ◯ | Marks an unselected option in a `MultiSelect` prompt | ## Versioning diff --git a/multiselect.go b/multiselect.go index 7fd6ca4b..24c4fb35 100644 --- a/multiselect.go +++ b/multiselect.go @@ -61,7 +61,7 @@ var MultiSelectQuestionTemplate = ` // OnChange is called on every keypress. func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - if key == terminal.KeyArrowUp { + if key == terminal.KeyArrowUp || key == 'k' { // if we are at the top of the list if m.selectedIndex == 0 { // go to the bottom @@ -70,7 +70,7 @@ func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, // decrement the selected index m.selectedIndex-- } - } else if key == terminal.KeyArrowDown { + } else if key == terminal.KeyArrowDown || key == 'j' { // if we are at the bottom of the list if m.selectedIndex == len(m.Options)-1 { // start at the top diff --git a/select.go b/select.go index 74cb77c3..eefe5cf0 100644 --- a/select.go +++ b/select.go @@ -61,8 +61,8 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo // if the user pressed the enter key if key == terminal.KeyEnter { return []rune(s.Options[s.selectedIndex]), 0, true - // if the user pressed the up arrow - } else if key == terminal.KeyArrowUp { + // if the user pressed the up arrow or 'k' to emulate vim + } else if key == terminal.KeyArrowUp || key == 'k' { s.useDefault = false // if we are at the top of the list @@ -73,8 +73,8 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo // otherwise we are not at the top of the list so decrement the selected index s.selectedIndex-- } - // if the user pressed down and there is room to move - } else if key == terminal.KeyArrowDown { + // if the user pressed down or 'j' to emulate vim + } else if key == terminal.KeyArrowDown || key == 'j' { s.useDefault = false // if we are at the bottom of the list if s.selectedIndex == len(s.Options)-1 { diff --git a/tests/multiselect.go b/tests/multiselect.go index 94537657..5227d207 100644 --- a/tests/multiselect.go +++ b/tests/multiselect.go @@ -35,6 +35,13 @@ var table = []TestUtil.TestTableEntry{ Default: []string{"Sundayaa"}, }, &answer, }, + { + "can navigate with j/k", &survey.MultiSelect{ + Message: "What days do you prefer:", + Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, + Default: []string{"Sundayaa"}, + }, &answer, + }, } func main() { diff --git a/tests/select.go b/tests/select.go index ba2c3570..5af02966 100644 --- a/tests/select.go +++ b/tests/select.go @@ -51,6 +51,12 @@ var goodTable = []TestUtil.TestTableEntry{ Options: []string{"red", "blue"}, }, &answer, }, + { + "can navigate with j/k", &survey.Select{ + Message: "Choose one:", + Options: []string{"red", "blue", "green"}, + }, &answer, + }, } var badTable = []TestUtil.TestTableEntry{