diff --git a/README.md b/README.md index 496c5a5..79de120 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ Also works fine in windows * [example/readline-demo](https://github.com/chzyer/readline/blob/master/example/readline-demo/readline-demo.go) The source code about the demo above +* [example/readline-im](https://github.com/chzyer/readline/blob/master/example/readline-im/readline-im.go) Example for how to write a IM program. + +* [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) Example for how to parse command which can submit by multiple time. + * [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go) A example about checking password strength, written by [@sahib](https://github.com/sahib) # Todo diff --git a/example/readline-im/readline-im.go b/example/readline-im/readline-im.go new file mode 100644 index 0000000..16803bd --- /dev/null +++ b/example/readline-im/readline-im.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "math/rand" + "time" + + "github.com/chzyer/readline" +) +import "log" + +func main() { + rl, err := readline.NewEx(&readline.Config{ + UniqueEditLine: true, + }) + if err != nil { + panic(err) + } + defer rl.Close() + + rl.SetPrompt("username: ") + username, err := rl.Readline() + if err != nil { + return + } + rl.ResetHistory() + log.SetOutput(rl.Stderr()) + + fmt.Fprintln(rl, "Hi,", username+"! My name is Dave.") + rl.SetPrompt(username + "> ") + + done := make(chan struct{}) + go func() { + rand.Seed(time.Now().Unix()) + loop: + for { + select { + case <-time.After(time.Duration(rand.Intn(20)) * 100 * time.Millisecond): + case <-done: + break loop + } + log.Println("Dave:", "hello") + } + log.Println("Dave:", "bye") + done <- struct{}{} + }() + + for { + ln := rl.Line() + if ln.CanContinue() { + continue + } else if ln.CanBreak() { + break + } + log.Println(username+":", ln.Line) + } + rl.Clean() + done <- struct{}{} + <-done +} diff --git a/history.go b/history.go index 64da6d2..e2e0235 100644 --- a/history.go +++ b/history.go @@ -37,6 +37,11 @@ func newOpHistory(cfg *Config) (o *opHistory) { return o } +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + func (o *opHistory) IsHistoryClosed() bool { return o.fd.Fd() == ^(uintptr(0)) } diff --git a/operation.go b/operation.go index 985103c..e4fa8f5 100644 --- a/operation.go +++ b/operation.go @@ -237,11 +237,16 @@ func (o *Operation) ioloop() { } // treat as EOF - o.buf.WriteString(o.cfg.EOFPrompt + "\n") + if !o.cfg.UniqueEditLine { + o.buf.WriteString(o.cfg.EOFPrompt + "\n") + } o.buf.Reset() isUpdateHistory = false o.history.Revert() o.errchan <- io.EOF + if o.cfg.UniqueEditLine { + o.buf.Clean() + } case CharInterrupt: if o.IsSearchMode() { o.t.KickRead() @@ -257,9 +262,13 @@ func (o *Operation) ioloop() { o.buf.MoveToLineEnd() o.buf.Refresh(nil) hint := o.cfg.InterruptPrompt + "\n" - o.buf.WriteString(hint) + if !o.cfg.UniqueEditLine { + o.buf.WriteString(hint) + } remain := o.buf.Reset() - remain = remain[:len(remain)-len([]rune(hint))] + if !o.cfg.UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } isUpdateHistory = false o.history.Revert() o.errchan <- &InterruptError{remain} @@ -419,6 +428,10 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) { return old, nil } +func (o *Operation) ResetHistory() { + o.history.Reset() +} + // if err is not nil, it just mean it fail to write to file // other things goes fine. func (o *Operation) SaveHistory(content string) error { @@ -431,6 +444,10 @@ func (o *Operation) Refresh() { } } +func (o *Operation) Clean() { + o.buf.Clean() +} + func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { return &DumpListener{f: f} } diff --git a/readline.go b/readline.go index 618a334..6ff97be 100644 --- a/readline.go +++ b/readline.go @@ -140,6 +140,10 @@ func New(prompt string) (*Instance, error) { return NewEx(&Config{Prompt: prompt}) } +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + func (i *Instance) SetPrompt(s string) { i.Operation.SetPrompt(s) } @@ -189,6 +193,24 @@ func (i *Instance) ReadPassword(prompt string) ([]byte, error) { return i.Operation.Password(prompt) } +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + // err is one of (nil, io.EOF, readline.ErrInterrupt) func (i *Instance) Readline() (string, error) { return i.Operation.String() @@ -211,6 +233,9 @@ func (i *Instance) Close() error { i.Operation.Close() return nil } +func (i *Instance) Clean() { + i.Operation.Clean() +} func (i *Instance) Write(b []byte) (int, error) { return i.Stdout().Write(b) diff --git a/runebuf.go b/runebuf.go index 11d8491..40450e8 100644 --- a/runebuf.go +++ b/runebuf.go @@ -20,9 +20,9 @@ type RuneBuffer struct { prompt []rune w io.Writer - cleanInScreen bool - interactive bool - cfg *Config + hadClean bool + interactive bool + cfg *Config width int @@ -374,7 +374,7 @@ func (r *RuneBuffer) Refresh(f func()) { func (r *RuneBuffer) print() { r.w.Write(r.output()) - r.cleanInScreen = false + r.hadClean = false } func (r *RuneBuffer) output() []byte { @@ -472,9 +472,9 @@ func (r *RuneBuffer) Clean() { } func (r *RuneBuffer) clean(idxLine int) { - if r.cleanInScreen || !r.interactive { + if r.hadClean || !r.interactive { return } - r.cleanInScreen = true + r.hadClean = true r.cleanOutput(r.w, idxLine) }