diff --git a/.travis.yml b/.travis.yml index b35dfc1..014a722 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,9 @@ language: go go: - 1.5 -before_install: - - go get golang.org/x/crypto/ssh/terminal + - 1.7 script: - GOOS=windows go install github.com/chzyer/readline/example/... - GOOS=linux go install github.com/chzyer/readline/example/... - GOOS=darwin go install github.com/chzyer/readline/example/... - - go test -v + - go test -race -v diff --git a/readline_test.go b/readline_test.go new file mode 100644 index 0000000..34a5a3b --- /dev/null +++ b/readline_test.go @@ -0,0 +1,27 @@ +package readline + +import ( + "testing" + "time" +) + +func TestRace(t *testing.T) { + rl, err := NewEx(&Config{}) + if err != nil { + t.Fatal(err) + return + } + + go func() { + for range time.Tick(time.Millisecond) { + rl.SetPrompt("hello") + } + }() + + go func() { + time.Sleep(100 * time.Millisecond) + rl.Close() + }() + + rl.Readline() +} diff --git a/runebuf.go b/runebuf.go index 545bc83..1b2a5d0 100644 --- a/runebuf.go +++ b/runebuf.go @@ -33,11 +33,15 @@ type RuneBuffer struct { } func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() r.width = newWidth + r.Unlock() } func (r *RuneBuffer) Backup() { + r.Lock() r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() } func (r *RuneBuffer) Restore() { @@ -62,15 +66,21 @@ func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuff } func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() r.cfg = cfg r.interactive = cfg.useInteractive() + r.Unlock() } func (r *RuneBuffer) SetMask(m rune) { + r.Lock() r.cfg.MaskRune = m + r.Unlock() } func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() return runes.WidthAll(r.buf[:x]) } @@ -86,6 +96,9 @@ func (r *RuneBuffer) promptLen() int { } func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + if i > 0 { rs := make([]rune, i) copy(rs, r.buf[r.idx:r.idx+i]) @@ -97,16 +110,22 @@ func (r *RuneBuffer) RuneSlice(i int) []rune { } func (r *RuneBuffer) Runes() []rune { + r.Lock() newr := make([]rune, len(r.buf)) copy(newr, r.buf) + r.Unlock() return newr } func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() return r.idx } func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() return len(r.buf) } @@ -154,6 +173,8 @@ func (r *RuneBuffer) MoveForward() { } func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() return r.idx == len(r.buf) } @@ -382,6 +403,12 @@ func (r *RuneBuffer) getSplitByLine(rs []rune) []string { } func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { if width == 0 { return 0 } @@ -404,7 +431,7 @@ func (r *RuneBuffer) Refresh(f func()) { return } - r.Clean() + r.clean() if f != nil { f() } @@ -527,10 +554,16 @@ func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { } func (r *RuneBuffer) Clean() { - r.clean(r.IdxLine(r.width)) + r.Lock() + r.clean() + r.Unlock() } -func (r *RuneBuffer) clean(idxLine int) { +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { if r.hadClean || !r.interactive { return }