diff --git a/example/readline-demo/readline-demo.go b/example/readline-demo/readline-demo.go index 2b1daf7..8b6f69f 100644 --- a/example/readline-demo/readline-demo.go +++ b/example/readline-demo/readline-demo.go @@ -72,7 +72,7 @@ func filterInput(r rune) (rune, bool) { func main() { l, err := readline.NewEx(&readline.Config{ - Prompt: "\033[31m»\033[0m ", + Prompt: readline.StaticPrompt("\033[31m»\033[0m "), HistoryFile: "/tmp/readline.tmp", AutoComplete: completer, InterruptPrompt: "^C", @@ -88,7 +88,7 @@ func main() { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line))) + l.SetPrompt(readline.StaticPrompt(fmt.Sprintf("Enter password(%v): ", len(line)))) l.Refresh() return nil, 0, false }) @@ -124,7 +124,7 @@ func main() { println("current mode: emacs") } case line == "login": - pswd, err := l.ReadPassword("please enter your password: ") + pswd, err := l.ReadPassword(readline.StaticPrompt("please enter your password: ")) if err != nil { break } @@ -141,7 +141,7 @@ func main() { log.Println("setprompt ") break } - l.SetPrompt(line[10:]) + l.SetPrompt(readline.StaticPrompt(line[10:])) case strings.HasPrefix(line, "say"): line := strings.TrimSpace(line[3:]) if len(line) == 0 { diff --git a/example/readline-im/readline-im.go b/example/readline-im/readline-im.go index 16803bd..b62d864 100644 --- a/example/readline-im/readline-im.go +++ b/example/readline-im/readline-im.go @@ -18,7 +18,7 @@ func main() { } defer rl.Close() - rl.SetPrompt("username: ") + rl.SetPrompt(readline.StaticPrompt("username: ")) username, err := rl.Readline() if err != nil { return @@ -27,7 +27,7 @@ func main() { log.SetOutput(rl.Stderr()) fmt.Fprintln(rl, "Hi,", username+"! My name is Dave.") - rl.SetPrompt(username + "> ") + rl.SetPrompt(readline.StaticPrompt(username + "> ")) done := make(chan struct{}) go func() { diff --git a/example/readline-multiline/readline-multiline.go b/example/readline-multiline/readline-multiline.go index 2192cf6..f47d032 100644 --- a/example/readline-multiline/readline-multiline.go +++ b/example/readline-multiline/readline-multiline.go @@ -8,7 +8,7 @@ import ( func main() { rl, err := readline.NewEx(&readline.Config{ - Prompt: "> ", + Prompt: readline.StaticPrompt("> "), HistoryFile: "/tmp/readline-multiline", DisableAutoSaveHistory: true, }) @@ -29,12 +29,12 @@ func main() { } cmds = append(cmds, line) if !strings.HasSuffix(line, ";") { - rl.SetPrompt(">>> ") + rl.SetPrompt(readline.StaticPrompt(">>> ")) continue } cmd := strings.Join(cmds, " ") cmds = cmds[:0] - rl.SetPrompt("> ") + rl.SetPrompt(readline.StaticPrompt("> ")) rl.SaveHistory(cmd) println(cmd) } diff --git a/example/readline-pass-strength/readline-pass-strength.go b/example/readline-pass-strength/readline-pass-strength.go index afcef45..24e8cf7 100644 --- a/example/readline-pass-strength/readline-pass-strength.go +++ b/example/readline-pass-strength/readline-pass-strength.go @@ -80,7 +80,7 @@ func createStrengthPrompt(password []rune) string { } func main() { - rl, err := readline.New("") + rl, err := readline.New(readline.StaticPrompt("")) if err != nil { return } @@ -88,7 +88,7 @@ func main() { setPasswordCfg := rl.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - rl.SetPrompt(createStrengthPrompt(line)) + rl.SetPrompt(readline.StaticPrompt(createStrengthPrompt(line))) rl.Refresh() return nil, 0, false }) diff --git a/example/readline-remote/readline-remote-server/server.go b/example/readline-remote/readline-remote-server/server.go index 38abc7d..a8f7a2a 100644 --- a/example/readline-remote/readline-remote-server/server.go +++ b/example/readline-remote/readline-remote-server/server.go @@ -8,7 +8,7 @@ import ( func main() { cfg := &readline.Config{ - Prompt: "readline-remote: ", + Prompt: readline.StaticPrompt("readline-remote: "), } handleFunc := func(rl *readline.Instance) { for { diff --git a/operation.go b/operation.go index 4c31624..94f52bf 100644 --- a/operation.go +++ b/operation.go @@ -2,6 +2,7 @@ package readline import ( "errors" + "fmt" "io" "sync" ) @@ -89,7 +90,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation { return op } -func (o *Operation) SetPrompt(s string) { +func (o *Operation) SetPrompt(s fmt.Stringer) { o.buf.SetPrompt(s) } @@ -398,7 +399,7 @@ func (o *Operation) Runes() ([]rune, error) { } } -func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { +func (o *Operation) PasswordEx(prompt fmt.Stringer, l Listener) ([]byte, error) { cfg := o.GenPasswordConfig() cfg.Prompt = prompt cfg.Listener = l @@ -417,7 +418,7 @@ func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { return o.Slice() } -func (o *Operation) Password(prompt string) ([]byte, error) { +func (o *Operation) Password(prompt fmt.Stringer) ([]byte, error) { return o.PasswordEx(prompt, nil) } diff --git a/prompt.go b/prompt.go new file mode 100644 index 0000000..0a19abf --- /dev/null +++ b/prompt.go @@ -0,0 +1,11 @@ +package readline + +type StaticPrompt string + +func (s StaticPrompt) String() string { + return string(s) +} + +func NewStaticPrompt(s string) StaticPrompt { + return StaticPrompt(s) +} diff --git a/readline.go b/readline.go index 0e7aca0..0c541e1 100644 --- a/readline.go +++ b/readline.go @@ -1,7 +1,7 @@ // Readline is a pure go implementation for GNU-Readline kind library. // // example: -// rl, err := readline.New("> ") +// rl, err := readline.New(readline.StaticPrompt("> ")) // if err != nil { // panic(err) // } @@ -17,7 +17,10 @@ // package readline -import "io" +import ( + "fmt" + "io" +) type Instance struct { Config *Config @@ -27,7 +30,7 @@ type Instance struct { type Config struct { // prompt supports ANSI escape sequence, so we can color some characters even in windows - Prompt string + Prompt fmt.Stringer // readline will persist historys to file where HistoryFile specified HistoryFile string @@ -94,6 +97,7 @@ func (c *Config) Init() error { if c.inited { return nil } + c.Prompt = StaticPrompt("") c.inited = true if c.Stdin == nil { c.Stdin = NewCancelableStdin(Stdin) @@ -175,7 +179,11 @@ func NewEx(cfg *Config) (*Instance, error) { }, nil } -func New(prompt string) (*Instance, error) { +type Prompter interface { + String() string +} + +func New(prompt fmt.Stringer) (*Instance, error) { return NewEx(&Config{Prompt: prompt}) } @@ -183,7 +191,7 @@ func (i *Instance) ResetHistory() { i.Operation.ResetHistory() } -func (i *Instance) SetPrompt(s string) { +func (i *Instance) SetPrompt(s fmt.Stringer) { i.Operation.SetPrompt(s) } @@ -224,11 +232,11 @@ func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { return i.Operation.PasswordWithConfig(cfg) } -func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { +func (i *Instance) ReadPasswordEx(prompt fmt.Stringer, l Listener) ([]byte, error) { return i.Operation.PasswordEx(prompt, l) } -func (i *Instance) ReadPassword(prompt string) ([]byte, error) { +func (i *Instance) ReadPassword(prompt fmt.Stringer) ([]byte, error) { return i.Operation.Password(prompt) } diff --git a/readline_test.go b/readline_test.go index 34a5a3b..e76d010 100644 --- a/readline_test.go +++ b/readline_test.go @@ -6,7 +6,9 @@ import ( ) func TestRace(t *testing.T) { - rl, err := NewEx(&Config{}) + rl, err := NewEx(&Config{ + Prompt: StaticPrompt(""), + }) if err != nil { t.Fatal(err) return @@ -14,7 +16,7 @@ func TestRace(t *testing.T) { go func() { for range time.Tick(time.Millisecond) { - rl.SetPrompt("hello") + rl.SetPrompt(StaticPrompt("hello")) } }() diff --git a/runebuf.go b/runebuf.go index 81d2da5..c9c59e8 100644 --- a/runebuf.go +++ b/runebuf.go @@ -3,6 +3,7 @@ package readline import ( "bufio" "bytes" + "fmt" "io" "strconv" "strings" @@ -17,7 +18,7 @@ type runeBufferBck struct { type RuneBuffer struct { buf []rune idx int - prompt []rune + prompt fmt.Stringer w io.Writer hadClean bool @@ -35,7 +36,7 @@ type RuneBuffer struct { sync.Mutex } -func (r* RuneBuffer) pushKill(text []rune) { +func (r *RuneBuffer) pushKill(text []rune) { r.lastKill = append([]rune{}, text...) } @@ -61,7 +62,7 @@ func (r *RuneBuffer) Restore() { }) } -func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { +func NewRuneBuffer(w io.Writer, prompt fmt.Stringer, cfg *Config, width int) *RuneBuffer { rb := &RuneBuffer{ w: w, interactive: cfg.useInteractive(), @@ -99,7 +100,7 @@ func (r *RuneBuffer) PromptLen() int { } func (r *RuneBuffer) promptLen() int { - return runes.WidthAll(runes.ColorFilter(r.prompt)) + return runes.WidthAll(runes.ColorFilter([]rune(r.prompt.String()))) } func (r *RuneBuffer) RuneSlice(i int) []rune { @@ -221,7 +222,7 @@ func (r *RuneBuffer) DeleteWord() { } for i := init + 1; i < len(r.buf); i++ { if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { - r.pushKill(r.buf[r.idx:i-1]) + r.pushKill(r.buf[r.idx : i-1]) r.Refresh(func() { r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) }) @@ -350,7 +351,7 @@ func (r *RuneBuffer) Yank() { return } r.Refresh(func() { - buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) + buf := make([]rune, 0, len(r.buf)+len(r.lastKill)) buf = append(buf, r.buf[:r.idx]...) buf = append(buf, r.lastKill...) buf = append(buf, r.buf[r.idx:]...) @@ -478,7 +479,7 @@ func (r *RuneBuffer) print() { func (r *RuneBuffer) output() []byte { buf := bytes.NewBuffer(nil) - buf.WriteString(string(r.prompt)) + buf.WriteString(r.prompt.String()) if r.cfg.EnableMask && len(r.buf) > 0 { buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) if r.buf[len(r.buf)-1] == '\n' { @@ -582,9 +583,9 @@ func (r *RuneBuffer) Set(buf []rune) { r.SetWithIdx(len(buf), buf) } -func (r *RuneBuffer) SetPrompt(prompt string) { +func (r *RuneBuffer) SetPrompt(prompt fmt.Stringer) { r.Lock() - r.prompt = []rune(prompt) + r.prompt = prompt //[]rune(prompt) r.Unlock() } diff --git a/std.go b/std.go index 61d44b7..63ca847 100644 --- a/std.go +++ b/std.go @@ -1,6 +1,7 @@ package readline import ( + "fmt" "io" "os" "sync" @@ -54,13 +55,13 @@ func AddHistory(content string) error { return ins.SaveHistory(content) } -func Password(prompt string) ([]byte, error) { +func Password(prompt fmt.Stringer) ([]byte, error) { ins := getInstance() return ins.ReadPassword(prompt) } // readline with global configs -func Line(prompt string) (string, error) { +func Line(prompt fmt.Stringer) (string, error) { ins := getInstance() ins.SetPrompt(prompt) return ins.Readline()