Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: console prompt conflict with bubble tea #2425

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 33 additions & 28 deletions internal/utils/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"os"
"strings"
"sync"
"time"

"github.com/go-errors/errors"
Expand All @@ -18,30 +19,50 @@ type Console struct {
stdin *bufio.Scanner
logger io.Writer
token chan string
mu sync.Mutex
}

func NewConsole() Console {
c := Console{
func NewConsole() *Console {
return &Console{
IsTTY: term.IsTerminal(int(os.Stdin.Fd())),
stdin: bufio.NewScanner(os.Stdin),
logger: GetDebugLogger(),
token: make(chan string),
mu: sync.Mutex{},
}
}

// Prevent interactive terminals from hanging more than 10 minutes
const ttyTimeout = time.Minute * 10

func (c *Console) ReadLine(ctx context.Context) string {
// Wait a few ms for input
timeout := time.Millisecond
if c.IsTTY {
timeout = ttyTimeout
}
timer := time.NewTimer(timeout)
defer timer.Stop()
// Read from stdin in background
go func() {
// Scan line by line from input or file
for c.stdin.Scan() {
c.mu.Lock()
defer c.mu.Unlock()
// Scan one line from input or file
if c.stdin.Scan() {
c.token <- strings.TrimSpace(c.stdin.Text())
}
if err := c.stdin.Err(); err != nil {
fmt.Fprintln(c.logger, err)
}
close(c.token)
}()
return c
var input string
select {
case input = <-c.token:
case <-ctx.Done():
case <-timer.C:
}
return input
}

// PromptYesNo asks yes/no questions using the label.
func (c Console) PromptYesNo(ctx context.Context, label string, def bool) (bool, error) {
func (c *Console) PromptYesNo(ctx context.Context, label string, def bool) (bool, error) {
choices := "Y/n"
if !def {
choices = "y/N"
Expand All @@ -68,26 +89,10 @@ func parseYesNo(s string) *bool {
return nil
}

// Prevent interactive terminals from hanging more than 10 minutes
const ttyTimeout = time.Minute * 10

// PromptText asks for input using the label.
func (c Console) PromptText(ctx context.Context, label string) (string, error) {
func (c *Console) PromptText(ctx context.Context, label string) (string, error) {
fmt.Fprint(os.Stderr, label)
// Wait a few ms for input
timeout := time.Millisecond
if c.IsTTY {
timeout = ttyTimeout
}
timer := time.NewTimer(timeout)
defer timer.Stop()
// Read from stdin
var input string
select {
case input = <-c.token:
case <-ctx.Done():
case <-timer.C:
}
input := c.ReadLine(ctx)
// Echo to stderr for non-interactive terminals
if !c.IsTTY {
fmt.Fprintln(os.Stderr, input)
Expand Down