From 853f3e26d5ef9bd352be7af4f5c2746fa6d06e40 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 9 Sep 2022 13:14:46 +0200 Subject: [PATCH 1/3] feat: add flag to disable filter/search for interactive printer --- interactive_multiselect_printer.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/interactive_multiselect_printer.go b/interactive_multiselect_printer.go index 941cddad3..ee15e6d6f 100644 --- a/interactive_multiselect_printer.go +++ b/interactive_multiselect_printer.go @@ -22,6 +22,7 @@ var ( MaxHeight: 5, Selector: ">", SelectorStyle: &ThemeDefault.SecondaryStyle, + Filter: true, } ) @@ -35,6 +36,7 @@ type InteractiveMultiselectPrinter struct { MaxHeight int Selector string SelectorStyle *Style + Filter bool selectedOption int selectedOptions []int @@ -70,6 +72,12 @@ func (p InteractiveMultiselectPrinter) WithMaxHeight(maxHeight int) *Interactive return &p } +// WithFilter sets the Filter option +func (p InteractiveMultiselectPrinter) WithFilter(filter bool) *InteractiveMultiselectPrinter { + p.Filter = filter + return &p +} + // Show shows the interactive multiselect menu and returns the selected entry. func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { // should be the first defer statement to make sure it is executed last @@ -126,13 +134,15 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { switch key { case keys.RuneKey: - // Fuzzy search for options - // append to fuzzy search string - p.fuzzySearchString += keyInfo.String() - p.selectedOption = 0 - p.displayedOptionsStart = 0 - p.displayedOptionsEnd = maxHeight - p.displayedOptions = append([]string{}, p.fuzzySearchMatches[:maxHeight]...) + if p.Filter { + // Fuzzy search for options + // append to fuzzy search string + p.fuzzySearchString += keyInfo.String() + p.selectedOption = 0 + p.displayedOptionsStart = 0 + p.displayedOptionsEnd = maxHeight + p.displayedOptions = append([]string{}, p.fuzzySearchMatches[:maxHeight]...) + } area.Update(p.renderSelectMenu()) case keys.Tab: if len(p.fuzzySearchMatches) == 0 { @@ -323,7 +333,11 @@ func (p *InteractiveMultiselectPrinter) renderSelectMenu() string { } } - content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s | type to %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all"), Bold.Sprint("filter")) + if p.Filter { + content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s | type to %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all"), Bold.Sprint("filter")) + } else { + content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all")) + } return content } From 24d0594ded67c4a3c8ec58b24635a415801fa364 Mon Sep 17 00:00:00 2001 From: Jochen Date: Fri, 9 Sep 2022 14:05:48 +0200 Subject: [PATCH 2/3] feat: custom select/confirm key for interactive printer --- .../demo_custom/README.md | 29 +++++++++ .../demo_custom/animation.svg | 10 ++++ .../interactive_multiselect/demo_custom/ci.go | 31 ++++++++++ .../demo_custom/main.go | 23 ++++++++ interactive_multiselect_printer.go | 59 +++++++++++++------ 5 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 _examples/interactive_multiselect/demo_custom/README.md create mode 100644 _examples/interactive_multiselect/demo_custom/animation.svg create mode 100644 _examples/interactive_multiselect/demo_custom/ci.go create mode 100644 _examples/interactive_multiselect/demo_custom/main.go diff --git a/_examples/interactive_multiselect/demo_custom/README.md b/_examples/interactive_multiselect/demo_custom/README.md new file mode 100644 index 000000000..7b8ff37fd --- /dev/null +++ b/_examples/interactive_multiselect/demo_custom/README.md @@ -0,0 +1,29 @@ +# interactive_multiselect/demo_custom + +![Animation](animation.svg) + +```go +package main + +import ( + "fmt" + + "atomicgo.dev/keyboard/keys" + "github.com/pterm/pterm" +) + +func main() { + var options []string + + for i := 0; i < 5; i++ { + options = append(options, fmt.Sprintf("Option %d", i)) + } + + printer := pterm.DefaultInteractiveMultiselect.WithOptions(options) + printer.NoFilter = true + printer.KeyConfirm = keys.Enter + printer.KeySelect = keys.Space + selectedOptions, _ := printer.Show() + pterm.Info.Printfln("Selected options: %s", pterm.Green(selectedOptions)) +} +``` diff --git a/_examples/interactive_multiselect/demo_custom/animation.svg b/_examples/interactive_multiselect/demo_custom/animation.svg new file mode 100644 index 000000000..3d8d397ec --- /dev/null +++ b/_examples/interactive_multiselect/demo_custom/animation.svg @@ -0,0 +1,10 @@ +Pleaseselectyouroptions:>[]Option0[]Option1[]Option2[]Option3[]Option4enter:select|tab:confirm|left:none|right:all|typetofilter[]Option0>[]Option1>[]Option1[]Option1>[]Option2>[]Option3[]Option3>[]Option5>[]Option5[]Option5>[]Option6[]Option6[]Option7>[]Option8[]Option8Pleaseselectyouroptions:f>[]Youcanusefuzzysearching(0)[]Youcanusefuzzysearching(1)[]Youcanusefuzzysearching(2)[]Youcanusefuzzysearching(3)[]Youcanusefuzzysearching(4)Pleaseselectyouroptions:fuzzy[]Youcanusefuzzysearching(0)>[]Youcanusefuzzysearching(2)>Option1>Option3>Option5>Option7>Option9>Youcanusefuzzysearching(2) INFO Selectedoptions:[Option1Option3Option5Option7Option9Youcanusefuzzysearching(2)]>[]Option3>[]Option4>[]Option7>[]Option7>[]Option9>[]Option9[]Option9>[]Option10Pleaseselectyouroptions:fuPleaseselectyouroptions:fuzPleaseselectyouroptions:fuzz>[]Youcanusefuzzysearching(1)>[]Youcanusefuzzysearching(2)Restartinganimation... \ No newline at end of file diff --git a/_examples/interactive_multiselect/demo_custom/ci.go b/_examples/interactive_multiselect/demo_custom/ci.go new file mode 100644 index 000000000..1fedeeae1 --- /dev/null +++ b/_examples/interactive_multiselect/demo_custom/ci.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "time" + + "atomicgo.dev/keyboard" + "atomicgo.dev/keyboard/keys" +) + +// ------ Automation for CI ------ +// You can ignore this function, it is used to automatically run the demo and generate the example animation in our CI system. +func init() { + if os.Getenv("CI") == "true" { + go func() { + time.Sleep(time.Second) + keyboard.SimulateKeyPress(keys.Down) + time.Sleep(time.Millisecond * 100) + keyboard.SimulateKeyPress(keys.Space) + + time.Sleep(time.Millisecond * 300) + + keyboard.SimulateKeyPress(keys.Down) + time.Sleep(time.Millisecond * 100) + keyboard.SimulateKeyPress(keys.Space) + + time.Sleep(time.Millisecond * 300) + keyboard.SimulateKeyPress(keys.Enter) + }() + } +} diff --git a/_examples/interactive_multiselect/demo_custom/main.go b/_examples/interactive_multiselect/demo_custom/main.go new file mode 100644 index 000000000..ba515001e --- /dev/null +++ b/_examples/interactive_multiselect/demo_custom/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + + "atomicgo.dev/keyboard/keys" + "github.com/pterm/pterm" +) + +func main() { + var options []string + + for i := 0; i < 5; i++ { + options = append(options, fmt.Sprintf("Option %d", i)) + } + + printer := pterm.DefaultInteractiveMultiselect.WithOptions(options) + printer.Filter = false + printer.KeyConfirm = keys.Enter + printer.KeySelect = keys.Space + selectedOptions, _ := printer.Show() + pterm.Info.Printfln("Selected options: %s", pterm.Green(selectedOptions)) +} diff --git a/interactive_multiselect_printer.go b/interactive_multiselect_printer.go index ee15e6d6f..dc6be6b2b 100644 --- a/interactive_multiselect_printer.go +++ b/interactive_multiselect_printer.go @@ -23,6 +23,8 @@ var ( Selector: ">", SelectorStyle: &ThemeDefault.SecondaryStyle, Filter: true, + KeySelect: keys.Enter, + KeyConfirm: keys.Tab, } ) @@ -46,6 +48,9 @@ type InteractiveMultiselectPrinter struct { displayedOptions []string displayedOptionsStart int displayedOptionsEnd int + + KeySelect keys.KeyCode + KeyConfirm keys.KeyCode } // WithOptions sets the options. @@ -78,6 +83,18 @@ func (p InteractiveMultiselectPrinter) WithFilter(filter bool) *InteractiveMulti return &p } +// WithKeySelect sets the confirm key +func (p InteractiveMultiselectPrinter) WithKeySelect(keySelect keys.KeyCode) *InteractiveMultiselectPrinter { + p.KeySelect = keySelect + return &p +} + +// WithKeyConfirm sets the confirm key +func (p InteractiveMultiselectPrinter) WithKeyConfirm(keyConfirm keys.KeyCode) *InteractiveMultiselectPrinter { + p.KeyConfirm = keyConfirm + return &p +} + // Show shows the interactive multiselect menu and returns the selected entry. func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { // should be the first defer statement to make sure it is executed last @@ -119,6 +136,10 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { return nil, fmt.Errorf("could not start area: %w", err) } + if p.Filter && (p.KeyConfirm == keys.Space || p.KeySelect == keys.Space) { + return nil, fmt.Errorf("if filter/search is active, keys.Space can not be used for KeySelect or KeyConfirm") + } + area.Update(p.renderSelectMenu()) cursor.Hide() @@ -133,6 +154,18 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { } switch key { + case p.KeyConfirm: + if len(p.fuzzySearchMatches) == 0 { + return false, nil + } + area.Update(p.renderFinishedMenu()) + return true, nil + case p.KeySelect: + if len(p.fuzzySearchMatches) > 0 { + // Select option if not already selected + p.selectOption(p.fuzzySearchMatches[p.selectedOption]) + } + area.Update(p.renderSelectMenu()) case keys.RuneKey: if p.Filter { // Fuzzy search for options @@ -144,16 +177,12 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { p.displayedOptions = append([]string{}, p.fuzzySearchMatches[:maxHeight]...) } area.Update(p.renderSelectMenu()) - case keys.Tab: - if len(p.fuzzySearchMatches) == 0 { - return false, nil - } - area.Update(p.renderFinishedMenu()) - return true, nil case keys.Space: - p.fuzzySearchString += " " - p.selectedOption = 0 - area.Update(p.renderSelectMenu()) + if p.Filter { + p.fuzzySearchString += " " + p.selectedOption = 0 + area.Update(p.renderSelectMenu()) + } case keys.Backspace: // Remove last character from fuzzy search string if len(p.fuzzySearchString) > 0 { @@ -236,12 +265,6 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { case keys.CtrlC: cancel() return true, nil - case keys.Enter: - if len(p.fuzzySearchMatches) > 0 { - // Select option if not already selected - p.selectOption(p.fuzzySearchMatches[p.selectedOption]) - } - area.Update(p.renderSelectMenu()) } return false, nil @@ -333,11 +356,11 @@ func (p *InteractiveMultiselectPrinter) renderSelectMenu() string { } } + help := fmt.Sprintf("%s: %s | %s: %s | left: %s | right: %s", p.KeySelect, Bold.Sprint("select"), p.KeyConfirm, Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all")) if p.Filter { - content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s | type to %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all"), Bold.Sprint("filter")) - } else { - content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all")) + help += fmt.Sprintf("| type to %s", Bold.Sprint("filter")) } + content += ThemeDefault.SecondaryStyle.Sprintfln(help) return content } From 0250cff47d7a6b20eb77322cf4d5bed4bc8d0dee Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Sun, 2 Oct 2022 01:04:34 +0200 Subject: [PATCH 3/3] refactored example --- .../{demo_custom => custom-keys}/ci.go | 0 .../{demo_custom => custom-keys}/main.go | 0 .../demo_custom/README.md | 29 ------------------- .../demo_custom/animation.svg | 10 ------- 4 files changed, 39 deletions(-) rename _examples/interactive_multiselect/{demo_custom => custom-keys}/ci.go (100%) rename _examples/interactive_multiselect/{demo_custom => custom-keys}/main.go (100%) delete mode 100644 _examples/interactive_multiselect/demo_custom/README.md delete mode 100644 _examples/interactive_multiselect/demo_custom/animation.svg diff --git a/_examples/interactive_multiselect/demo_custom/ci.go b/_examples/interactive_multiselect/custom-keys/ci.go similarity index 100% rename from _examples/interactive_multiselect/demo_custom/ci.go rename to _examples/interactive_multiselect/custom-keys/ci.go diff --git a/_examples/interactive_multiselect/demo_custom/main.go b/_examples/interactive_multiselect/custom-keys/main.go similarity index 100% rename from _examples/interactive_multiselect/demo_custom/main.go rename to _examples/interactive_multiselect/custom-keys/main.go diff --git a/_examples/interactive_multiselect/demo_custom/README.md b/_examples/interactive_multiselect/demo_custom/README.md deleted file mode 100644 index 7b8ff37fd..000000000 --- a/_examples/interactive_multiselect/demo_custom/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# interactive_multiselect/demo_custom - -![Animation](animation.svg) - -```go -package main - -import ( - "fmt" - - "atomicgo.dev/keyboard/keys" - "github.com/pterm/pterm" -) - -func main() { - var options []string - - for i := 0; i < 5; i++ { - options = append(options, fmt.Sprintf("Option %d", i)) - } - - printer := pterm.DefaultInteractiveMultiselect.WithOptions(options) - printer.NoFilter = true - printer.KeyConfirm = keys.Enter - printer.KeySelect = keys.Space - selectedOptions, _ := printer.Show() - pterm.Info.Printfln("Selected options: %s", pterm.Green(selectedOptions)) -} -``` diff --git a/_examples/interactive_multiselect/demo_custom/animation.svg b/_examples/interactive_multiselect/demo_custom/animation.svg deleted file mode 100644 index 3d8d397ec..000000000 --- a/_examples/interactive_multiselect/demo_custom/animation.svg +++ /dev/null @@ -1,10 +0,0 @@ -Pleaseselectyouroptions:>[]Option0[]Option1[]Option2[]Option3[]Option4enter:select|tab:confirm|left:none|right:all|typetofilter[]Option0>[]Option1>[]Option1[]Option1>[]Option2>[]Option3[]Option3>[]Option5>[]Option5[]Option5>[]Option6[]Option6[]Option7>[]Option8[]Option8Pleaseselectyouroptions:f>[]Youcanusefuzzysearching(0)[]Youcanusefuzzysearching(1)[]Youcanusefuzzysearching(2)[]Youcanusefuzzysearching(3)[]Youcanusefuzzysearching(4)Pleaseselectyouroptions:fuzzy[]Youcanusefuzzysearching(0)>[]Youcanusefuzzysearching(2)>Option1>Option3>Option5>Option7>Option9>Youcanusefuzzysearching(2) INFO Selectedoptions:[Option1Option3Option5Option7Option9Youcanusefuzzysearching(2)]>[]Option3>[]Option4>[]Option7>[]Option7>[]Option9>[]Option9[]Option9>[]Option10Pleaseselectyouroptions:fuPleaseselectyouroptions:fuzPleaseselectyouroptions:fuzz>[]Youcanusefuzzysearching(1)>[]Youcanusefuzzysearching(2)Restartinganimation... \ No newline at end of file