diff --git a/README.md b/README.md
index 7433dc04a..eb0acfee2 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ which features automatic website generation, automatic deployments, a custom CI-
|✏ Documentation |To view the official documentation of the latest release, you can go to the automatically generated page of [pkg.go.dev](https://pkg.go.dev/github.com/pterm/pterm#section-documentation) This documentation is very technical and includes every method that can be used in PTerm. **For an easy start we recommend that you take a look at the [examples section](#-examples).** Here you can see pretty much every feature of PTerm with example code. The animations of the examples are automatically updated as soon as something changes in PTerm.|
-
+
### Printers (Components)
|Feature|Examples| - |Feature|Examples|
@@ -113,7 +113,7 @@ which features automatic website generation, automatic deployments, a custom CI-
|---|---|---|
|![Jens Lauterbach](https://avatars.githubusercontent.com/u/1292368?s=25)|[@jenslauterbach](https://github.com/jenslauterbach)|25$|
-
+
## 🧪 Examples
@@ -1066,6 +1066,31 @@ func boolToText(b bool) string {
+### interactive_continue/demo
+
+![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_continue/demo/animation.svg)
+
+
+
+SHOW SOURCE
+
+```go
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
+
+```
+
+
+
### interactive_multiselect/demo
![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_multiselect/demo/animation.svg)
@@ -1608,7 +1633,7 @@ func main() {
-
+
---
> GitHub [@pterm](https://github.com/pterm) ·
diff --git a/_examples/README.md b/_examples/README.md
index 12dad6280..41cc98480 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -938,6 +938,31 @@ func boolToText(b bool) string {
+### interactive_continue/demo
+
+![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_continue/demo/animation.svg)
+
+
+
+SHOW SOURCE
+
+```go
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
+
+```
+
+
+
### interactive_multiselect/demo
![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_multiselect/demo/animation.svg)
diff --git a/_examples/interactive_continue/README.md b/_examples/interactive_continue/README.md
new file mode 100644
index 000000000..3aa4b33a1
--- /dev/null
+++ b/_examples/interactive_continue/README.md
@@ -0,0 +1,25 @@
+### interactive_continue/demo
+
+![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_continue/demo/animation.svg)
+
+
+
+SHOW SOURCE
+
+```go
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
+
+```
+
+
+
diff --git a/_examples/interactive_continue/demo/README.md b/_examples/interactive_continue/demo/README.md
new file mode 100644
index 000000000..f4c20783a
--- /dev/null
+++ b/_examples/interactive_continue/demo/README.md
@@ -0,0 +1,18 @@
+# interactive_continue/demo
+
+![Animation](animation.svg)
+
+```go
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
+
+```
diff --git a/_examples/interactive_continue/demo/animation.svg b/_examples/interactive_continue/demo/animation.svg
new file mode 100644
index 000000000..5ec0f59cd
--- /dev/null
+++ b/_examples/interactive_continue/demo/animation.svg
@@ -0,0 +1,10 @@
+
diff --git a/_examples/interactive_continue/demo/ci.go b/_examples/interactive_continue/demo/ci.go
new file mode 100644
index 000000000..2ba46081b
--- /dev/null
+++ b/_examples/interactive_continue/demo/ci.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "os"
+ "time"
+
+ "atomicgo.dev/keyboard"
+)
+
+// ------ 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 * 2)
+ keyboard.SimulateKeyPress('y')
+ }()
+ }
+}
diff --git a/_examples/interactive_continue/demo/main.go b/_examples/interactive_continue/demo/main.go
new file mode 100644
index 000000000..6684bdf90
--- /dev/null
+++ b/_examples/interactive_continue/demo/main.go
@@ -0,0 +1,11 @@
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
diff --git a/docs/README.md b/docs/README.md
index 7433dc04a..eb0acfee2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -90,7 +90,7 @@ which features automatic website generation, automatic deployments, a custom CI-
|✏ Documentation |To view the official documentation of the latest release, you can go to the automatically generated page of [pkg.go.dev](https://pkg.go.dev/github.com/pterm/pterm#section-documentation) This documentation is very technical and includes every method that can be used in PTerm. **For an easy start we recommend that you take a look at the [examples section](#-examples).** Here you can see pretty much every feature of PTerm with example code. The animations of the examples are automatically updated as soon as something changes in PTerm.|
-
+
### Printers (Components)
|Feature|Examples| - |Feature|Examples|
@@ -113,7 +113,7 @@ which features automatic website generation, automatic deployments, a custom CI-
|---|---|---|
|![Jens Lauterbach](https://avatars.githubusercontent.com/u/1292368?s=25)|[@jenslauterbach](https://github.com/jenslauterbach)|25$|
-
+
## 🧪 Examples
@@ -1066,6 +1066,31 @@ func boolToText(b bool) string {
+### interactive_continue/demo
+
+![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_continue/demo/animation.svg)
+
+
+
+SHOW SOURCE
+
+```go
+package main
+
+import (
+ "github.com/pterm/pterm"
+)
+
+func main() {
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ pterm.Println() // Blank line
+ pterm.Info.Printfln("You answered: %s", result)
+}
+
+```
+
+
+
### interactive_multiselect/demo
![Animation](https://raw.githubusercontent.com/pterm/pterm/master/_examples/interactive_multiselect/demo/animation.svg)
@@ -1608,7 +1633,7 @@ func main() {
-
+
---
> GitHub [@pterm](https://github.com/pterm) ·
diff --git a/interactive_continue_printer.go b/interactive_continue_printer.go
new file mode 100644
index 000000000..994e513b6
--- /dev/null
+++ b/interactive_continue_printer.go
@@ -0,0 +1,178 @@
+package pterm
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "atomicgo.dev/cursor"
+ "atomicgo.dev/keyboard"
+ "atomicgo.dev/keyboard/keys"
+)
+
+var (
+ // DefaultInteractiveContinue is the default InteractiveContinue printer.
+ // Pressing "y" will return yes, "n" will return no, "a" returns all and "s" returns stop.
+ // Pressing enter without typing any letter will return the configured default value (by default set to "yes", the fisrt option).
+ DefaultInteractiveContinue = InteractiveContinuePrinter{
+ DefaultValueIndex: 0,
+ DefaultText: "Do you want to continue",
+ TextStyle: &ThemeDefault.PrimaryStyle,
+ Options: []string{"yes", "no", "all", "stop"},
+ OptionsStyle: &ThemeDefault.SuccessMessageStyle,
+ SuffixStyle: &ThemeDefault.SecondaryStyle,
+ }
+)
+
+// InteractiveContinuePrinter is a printer for interactive continue prompts.
+type InteractiveContinuePrinter struct {
+ DefaultValueIndex int
+ DefaultText string
+ TextStyle *Style
+ Options []string
+ OptionsStyle *Style
+ Handles []string
+ ShowFullHandles bool
+ SuffixStyle *Style
+}
+
+// WithDefaultText sets the default text.
+func (p InteractiveContinuePrinter) WithDefaultText(text string) *InteractiveContinuePrinter {
+ p.DefaultText = text
+ return &p
+}
+
+// WithDefaultValueIndex sets the default value, which will be returned when the user presses enter without typing any letter.
+func (p InteractiveContinuePrinter) WithDefaultValueIndex(value int) *InteractiveContinuePrinter {
+ if value >= len(p.Options) {
+ panic("Index out of range")
+ }
+ p.DefaultValueIndex = value
+ return &p
+}
+
+// WithDefaultValue sets the default value, which will be returned when the user presses enter without typing any letter.
+func (p InteractiveContinuePrinter) WithDefaultValue(value string) *InteractiveContinuePrinter {
+ for i, o := range p.Options {
+ if o == value {
+ p.DefaultValueIndex = i
+ break
+ }
+ }
+ return &p
+}
+
+// WithTextStyle sets the text style.
+func (p InteractiveContinuePrinter) WithTextStyle(style *Style) *InteractiveContinuePrinter {
+ p.TextStyle = style
+ return &p
+}
+
+// WithOptions sets the options.
+func (p InteractiveContinuePrinter) WithOptions(options []string) *InteractiveContinuePrinter {
+ p.Options = options
+ return &p
+}
+
+// WithHandles allows you to customize the short handles for the answers.
+func (p InteractiveContinuePrinter) WithHandles(handles []string) *InteractiveContinuePrinter {
+ if len(handles) != len(p.Options) {
+ panic("Invalid number of handles")
+ }
+ p.Handles = handles
+ return &p
+}
+
+// WithFullHandles will set ShowFullHandles to true
+// this makes the printer display the full options instead their shorthand version.
+func (p InteractiveContinuePrinter) WithFullHandles() *InteractiveContinuePrinter {
+ p.ShowFullHandles = true
+ return &p
+}
+
+// WithOptionsStyle sets the continue style.
+func (p InteractiveContinuePrinter) WithOptionsStyle(style *Style) *InteractiveContinuePrinter {
+ p.OptionsStyle = style
+ return &p
+}
+
+// WithSuffixStyle sets the suffix style.
+func (p InteractiveContinuePrinter) WithSuffixStyle(style *Style) *InteractiveContinuePrinter {
+ p.SuffixStyle = style
+ return &p
+}
+
+// Show shows the continue prompt.
+//
+// Example:
+// result, _ := pterm.DefaultInteractiveContinue.Show("Do you want to apply the changes?")
+// pterm.Println(result)
+func (p InteractiveContinuePrinter) Show(text ...string) (string, error) {
+ var result string
+
+ if len(text) == 0 || text[0] == "" {
+ text = []string{p.DefaultText}
+ }
+
+ if p.ShowFullHandles {
+ p.Handles = p.Options
+ }
+
+ if p.Handles == nil || len(p.Handles) == 0 {
+ p.Handles = p.getDefaultHandles()
+ }
+
+ p.TextStyle.Print(text[0] + " " + p.getSuffix() + ": ")
+
+ err := keyboard.Listen(func(keyInfo keys.Key) (stop bool, err error) {
+ if err != nil {
+ return false, fmt.Errorf("failed to get key: %w", err)
+ }
+ key := keyInfo.Code
+ char := keyInfo.String()
+
+ switch key {
+ case keys.RuneKey:
+ for i, c := range p.Handles {
+ if p.ShowFullHandles {
+ c = string([]rune(c)[0])
+ }
+ if char == c || (i == p.DefaultValueIndex && strings.EqualFold(c, char)) {
+ Println()
+ result = p.Options[i]
+ return true, nil
+ }
+ }
+ case keys.Enter:
+ Println()
+ result = p.Options[p.DefaultValueIndex]
+ return true, nil
+ case keys.CtrlC:
+ os.Exit(1)
+ return true, nil
+ }
+ return false, nil
+ })
+ cursor.StartOfLine()
+ return result, err
+}
+
+// getDefaultHandles returns the short hand answers for the continueation prompt
+func (p InteractiveContinuePrinter) getDefaultHandles() []string {
+ handles := []string{}
+ for _, option := range p.Options {
+ handles = append(handles, strings.ToLower(string([]rune(option)[0])))
+ }
+ handles[p.DefaultValueIndex] = strings.ToUpper(handles[p.DefaultValueIndex])
+
+ return handles
+}
+
+// getSuffix returns the continueation prompt suffix
+func (p InteractiveContinuePrinter) getSuffix() string {
+ if p.Handles == nil || len(p.Handles) != len(p.Options) {
+ panic("Handles not initialized")
+ }
+
+ return p.SuffixStyle.Sprintf("[%s]", strings.Join(p.Handles, "/"))
+}
diff --git a/interactive_continue_printer_test.go b/interactive_continue_printer_test.go
new file mode 100644
index 000000000..57e21edaf
--- /dev/null
+++ b/interactive_continue_printer_test.go
@@ -0,0 +1,183 @@
+package pterm_test
+
+import (
+ "testing"
+
+ "atomicgo.dev/keyboard"
+ "atomicgo.dev/keyboard/keys"
+ "github.com/MarvinJWendt/testza"
+
+ "github.com/pterm/pterm"
+)
+
+func TestInteractiveContinuePrinter_Show_yes(t *testing.T) {
+ go func() {
+ keyboard.SimulateKeyPress('y')
+ }()
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ testza.AssertEqual(t, result, "yes")
+ go func() {
+ keyboard.SimulateKeyPress('Y')
+ }()
+ result, _ = pterm.DefaultInteractiveContinue.Show()
+ testza.AssertEqual(t, result, "yes")
+}
+
+func TestInteractiveContinuePrinter_Show_no(t *testing.T) {
+ go func() {
+ keyboard.SimulateKeyPress('n')
+ }()
+ result, _ := pterm.DefaultInteractiveContinue.Show()
+ testza.AssertEqual(t, result, "no")
+}
+
+func TestInteractiveContinuePrinter_WithDefaultValueIndes(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithDefaultValueIndex(1)
+ testza.AssertEqual(t, p.DefaultValueIndex, 1)
+}
+
+func TestInteractiveContinuePrinter_WithDefaultValue_yes(t *testing.T) {
+ go func() {
+ keyboard.SimulateKeyPress(keys.Enter)
+ }()
+ p := pterm.DefaultInteractiveContinue.WithDefaultValue("yes")
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, "yes")
+}
+
+func TestInteractiveContinuePrinter_WithDefaultValue_no(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithDefaultValue("no")
+ go func() {
+ keyboard.SimulateKeyPress(keys.Enter)
+ }()
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, "no")
+ go func() {
+ keyboard.SimulateKeyPress('n')
+ }()
+ result, _ = p.Show()
+ testza.AssertEqual(t, result, "no")
+ go func() {
+ keyboard.SimulateKeyPress('N')
+ }()
+ result, _ = p.Show()
+ testza.AssertEqual(t, result, "no")
+}
+
+func TestInteractiveContinuePrinter_WithFullHandles(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithFullHandles()
+ testza.AssertTrue(t, p.ShowFullHandles)
+ go func() {
+ keyboard.SimulateKeyPress('n')
+ }()
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, "no")
+}
+
+func TestInteractiveContinuePrinter_WithOptionsStyle(t *testing.T) {
+ style := pterm.NewStyle(pterm.FgRed)
+ p := pterm.DefaultInteractiveContinue.WithOptionsStyle(style)
+ testza.AssertEqual(t, p.OptionsStyle, style)
+}
+
+func TestInteractiveContinuePrinter_WithOptions(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithOptions([]string{"next", "stop", "continue"})
+ testza.AssertEqual(t, p.Options, []string{"next", "stop", "continue"})
+}
+
+func TestInteractiveContinuePrinter_WithHandles(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithOptions([]string{"yes", "no", "always", "never"}).WithHandles([]string{"y", "n", "a", "N"})
+ testza.AssertEqual(t, p.Handles, []string{"y", "n", "a", "N"})
+ tests := []struct {
+ name string
+ key rune
+ expected string
+ }{
+ {
+ name: "Yes",
+ key: 'y',
+ expected: "yes",
+ },
+ {
+ name: "No",
+ key: 'n',
+ expected: "no",
+ },
+ {
+ name: "Always",
+ key: 'a',
+ expected: "always",
+ },
+ {
+ name: "Never",
+ key: 'N',
+ expected: "never",
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ go func() {
+ keyboard.SimulateKeyPress(tc.key)
+ }()
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, tc.expected)
+ })
+ }
+ p.DefaultValueIndex = 1
+ go func() {
+ keyboard.SimulateKeyPress(keys.Enter)
+ }()
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, "no")
+}
+
+func TestInteractiveContinuePrinter_WithDefaultText(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithDefaultText("default")
+ testza.AssertEqual(t, p.DefaultText, "default")
+}
+
+func TestInteractiveContinuePrinter_CustomAnswers(t *testing.T) {
+ p := pterm.DefaultInteractiveContinue.WithOptions([]string{"next", "stop", "continue"})
+ tests := []struct {
+ name string
+ key rune
+ expected string
+ }{
+ {
+ name: "Next",
+ key: 'n',
+ expected: "next",
+ },
+ {
+ name: "Stop",
+ key: 's',
+ expected: "stop",
+ },
+ {
+ name: "Continue",
+ key: 'c',
+ expected: "continue",
+ },
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ go func() {
+ keyboard.SimulateKeyPress(tc.key)
+ }()
+ result, _ := p.Show()
+ testza.AssertEqual(t, result, tc.expected)
+ })
+ }
+}
+
+func TestInteractiveContinuePrinter_WithSuffixStyle(t *testing.T) {
+ style := pterm.NewStyle(pterm.FgRed)
+ p := pterm.DefaultInteractiveContinue.WithSuffixStyle(style)
+ testza.AssertEqual(t, p.SuffixStyle, style)
+}
+
+func TestInteractiveContinuePrinter_WithTextStyle(t *testing.T) {
+ style := pterm.NewStyle(pterm.FgRed)
+ p := pterm.DefaultInteractiveContinue.WithTextStyle(style)
+ testza.AssertEqual(t, p.TextStyle, style)
+}