diff --git a/fs/config/config_read_password.go b/fs/config/config_read_password.go index 688126fb1..9d302a14f 100644 --- a/fs/config/config_read_password.go +++ b/fs/config/config_read_password.go @@ -17,7 +17,7 @@ import ( func ReadPassword() string { stdin := int(os.Stdin.Fd()) if !terminal.IsTerminal(stdin) { - return ReadLine() + return ReadLine("") } line, err := terminal.ReadPassword(stdin) _, _ = fmt.Fprintln(os.Stderr) diff --git a/fs/config/config_read_password_unsupported.go b/fs/config/config_read_password_unsupported.go index a9d8a6702..8918ce425 100644 --- a/fs/config/config_read_password_unsupported.go +++ b/fs/config/config_read_password_unsupported.go @@ -7,5 +7,5 @@ package config // ReadPassword reads a password with echoing it to the terminal. func ReadPassword() string { - return ReadLine() + return ReadLine("") } diff --git a/fs/config/ui.go b/fs/config/ui.go index 226a8f832..6e31923fe 100644 --- a/fs/config/ui.go +++ b/fs/config/ui.go @@ -3,7 +3,6 @@ package config import ( - "bufio" "context" "errors" "fmt" @@ -15,6 +14,7 @@ import ( "strings" "unicode/utf8" + "github.com/peterh/liner" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configstruct" @@ -25,12 +25,22 @@ import ( "golang.org/x/text/unicode/norm" ) -// ReadLine reads some input -var ReadLine = func() string { - buf := bufio.NewReader(os.Stdin) - line, err := buf.ReadString('\n') - if err != nil && (line == "" || err != io.EOF) { - fs.Fatalf(nil, "Failed to read line: %v", err) +// ReadLine reads an unlimited length line from stdin with a prompt. +var ReadLine = func(prompt string) string { + l := liner.NewLiner() + defer func() { + _ = l.Close() + }() + l.SetMultiLineMode(true) + l.SetCtrlCAborts(true) + + line, err := l.Prompt(prompt) + if err == io.EOF { + return "" + } + if err != nil { + _ = l.Close() + fs.Fatalf(nil, "Failed to read: %v", err) } return strings.TrimSpace(line) } @@ -39,8 +49,7 @@ var ReadLine = func() string { func ReadNonEmptyLine(prompt string) string { result := "" for result == "" { - fmt.Print(prompt) - result = strings.TrimSpace(ReadLine()) + result = strings.TrimSpace(ReadLine(prompt)) } return result } @@ -63,8 +72,7 @@ func CommandDefault(commands []string, defaultIndex int) byte { optString := strings.Join(opts, "") optHelp := strings.Join(opts, "/") for { - fmt.Printf("%s> ", optHelp) - result := strings.ToLower(ReadLine()) + result := strings.ToLower(ReadLine(fmt.Sprintf("%s> ", optHelp))) if len(result) == 0 { if defaultIndex >= 0 { return optString[defaultIndex] @@ -146,8 +154,7 @@ func Choose(what string, kind string, choices, help []string, defaultValue strin terminal.WriteString(terminal.Reset) } for { - fmt.Printf("%s> ", what) - result := ReadLine() + result := ReadLine(fmt.Sprintf("%s> ", what)) i, err := strconv.Atoi(result) if err != nil { if slices.Contains(choices, result) { @@ -194,8 +201,7 @@ func Enter(what string, kind string, defaultValue string, required bool) string fmt.Println() } for { - fmt.Printf("%s> ", what) - result := ReadLine() + result := ReadLine(fmt.Sprintf("%s> ", what)) if !required || result != "" { return result } @@ -254,8 +260,7 @@ func ChoosePassword(defaultValue string, required bool) string { // inclusive prompting them with what. func ChooseNumber(what string, min, max int) int { for { - fmt.Printf("%s> ", what) - result := ReadLine() + result := ReadLine(fmt.Sprintf("%s> ", what)) i, err := strconv.Atoi(result) if err != nil { fmt.Printf("Bad number: %v\n", err) @@ -526,8 +531,7 @@ func ChooseOption(o *fs.Option, name string) string { func NewRemoteName() (name string) { for { fmt.Println("Enter name for new remote.") - fmt.Printf("name> ") - name = ReadLine() + name = ReadLine("name> ") if LoadedData().HasSection(name) { fmt.Printf("Remote %q already exists.\n", name) continue diff --git a/fs/config/ui_test.go b/fs/config/ui_test.go index eac3a9bb0..7320aca47 100644 --- a/fs/config/ui_test.go +++ b/fs/config/ui_test.go @@ -85,9 +85,9 @@ func testConfigFile(t *testing.T, options []fs.Option, configFileName string) fu // makeReadLine makes a simple readLine which returns a fixed list of // strings -func makeReadLine(answers []string) func() string { +func makeReadLine(answers []string) func(string) string { i := 0 - return func() string { + return func(string) string { i++ return answers[i-1] } diff --git a/go.mod b/go.mod index 106a293a8..8cb3066b9 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,7 @@ require ( github.com/ncw/swift/v2 v2.0.4 github.com/oracle/oci-go-sdk/v65 v65.98.0 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/peterh/liner v1.2.2 github.com/pkg/sftp v1.13.9 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.23.0 diff --git a/go.sum b/go.sum index f4ce2432b..409c60c5a 100644 --- a/go.sum +++ b/go.sum @@ -454,6 +454,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= @@ -500,6 +501,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 h1:XeOYlK9W1uCmhjJSsY78Mcuh7MVkNjTzmHx1yBzizSU= github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14/go.mod h1:jVblp62SafmidSkvWrXyxAme3gaTfEtWwRPGz5cpvHg= +github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= +github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -845,6 +848,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=