From b5668d590fdeb50a09f1a8f6258e8a052ff0c273 Mon Sep 17 00:00:00 2001 From: Jochen Date: Fri, 9 Sep 2022 14:05:48 +0200 Subject: [PATCH] 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 | 49 ++++++++++++------- 5 files changed, 123 insertions(+), 19 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..c91a82dfc --- /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.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/interactive_multiselect_printer.go b/interactive_multiselect_printer.go index 55f86a7e8..ff9c0376c 100644 --- a/interactive_multiselect_printer.go +++ b/interactive_multiselect_printer.go @@ -22,6 +22,8 @@ var ( MaxHeight: 5, Selector: ">", SelectorStyle: &ThemeDefault.SecondaryStyle, + KeySelect: keys.Enter, + KeyConfirm: keys.Tab, } ) @@ -45,6 +47,9 @@ type InteractiveMultiselectPrinter struct { displayedOptions []string displayedOptionsStart int displayedOptionsEnd int + + KeySelect keys.KeyCode + KeyConfirm keys.KeyCode } // WithOptions sets the options. @@ -118,6 +123,10 @@ func (p *InteractiveMultiselectPrinter) Show(text ...string) ([]string, error) { return nil, fmt.Errorf("could not start area: %w", err) } + if !p.NoFilter && (p.KeyConfirm == keys.Space || p.KeySelect == keys.Space) { + return nil, fmt.Errorf("if filter/search is active, keys.Space can not use for KeySelect or KeyConfirm") + } + area.Update(p.renderSelectMenu()) cursor.Hide() @@ -132,6 +141,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.NoFilter { // Fuzzy search for options @@ -143,16 +164,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.NoFilter { + p.fuzzySearchString += " " + p.selectedOption = 0 + area.Update(p.renderSelectMenu()) + } case keys.Backspace: // Remove last character from fuzzy search string if len(p.fuzzySearchString) > 0 { @@ -235,12 +252,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 @@ -332,11 +343,11 @@ func (p *InteractiveMultiselectPrinter) renderSelectMenu() string { } } - if p.NoFilter { - content += ThemeDefault.SecondaryStyle.Sprintfln("enter: %s | tab: %s | left: %s | right: %s", Bold.Sprint("select"), Bold.Sprint("confirm"), Bold.Sprint("none"), Bold.Sprint("all")) - } else { - 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")) + 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.NoFilter { + help += fmt.Sprintf("| type to %s", Bold.Sprint("filter")) } + content += ThemeDefault.SecondaryStyle.Sprintfln(help) return content }