diff --git a/ctx.go b/ctx.go index 5371356..ef1c9e5 100644 --- a/ctx.go +++ b/ctx.go @@ -276,7 +276,7 @@ func (c *Ctx) DrawPrompt() { } func (c *Ctx) NewBufferReader(r io.ReadCloser) *BufferReader { - return &BufferReader{c, r, make(chan struct{})} + return &BufferReader{c, r, make(chan struct{}, 1)} } func (c *Ctx) NewView() *View { diff --git a/debug_on.go b/debug_on.go index 8d6b2f9..d816b3e 100644 --- a/debug_on.go +++ b/debug_on.go @@ -3,7 +3,6 @@ package peco import ( - "fmt" "log" "os" "runtime" @@ -12,12 +11,19 @@ import ( ) const debug = true + var tracer *log.Logger +var mutexTracer *log.Logger + func init() { if v, err := strconv.ParseBool(os.Getenv("PECO_TRACE")); err == nil && v { tracer = log.New(os.Stderr, "peco: ", log.LstdFlags) tracer.Printf("==== INITIALIZED tracer ====") } + if v, err := strconv.ParseBool(os.Getenv("PECO_LOCK_TRACE")); err == nil && v { + mutexTracer = log.New(os.Stderr, "mutex: ", log.LstdFlags) + mutexTracer.Printf("==== INITIALIZED mutext tracer ====") + } } func trace(f string, args ...interface{}) { @@ -27,6 +33,13 @@ func trace(f string, args ...interface{}) { tracer.Printf(f, args...) } +func mutexTrace(f string, args ...interface{}) { + if mutexTracer == nil { + return + } + mutexTracer.Printf(f, args...) +} + func newMutex() sync.Locker { return &loggingMutex{&sync.Mutex{}} } @@ -38,13 +51,13 @@ type loggingMutex struct { func (m *loggingMutex) Lock() { buf := make([]byte, 8092) l := runtime.Stack(buf, false) - fmt.Printf("LOCK %s\n", buf[:l]) + mutexTrace("LOCK %s\n", buf[:l]) m.Mutex.Lock() } func (m *loggingMutex) Unlock() { buf := make([]byte, 8092) l := runtime.Stack(buf, false) - fmt.Printf("UNLOCK %s\n", buf[:l]) + mutexTrace("UNLOCK %s\n", buf[:l]) m.Mutex.Unlock() } diff --git a/reader.go b/reader.go index 4bad457..26c51c4 100644 --- a/reader.go +++ b/reader.go @@ -8,6 +8,8 @@ import ( "time" ) +// BufferReader reads from either stdin or a file. In case of stdin, +// it also handles possible infinite source. type BufferReader struct { *Ctx input io.ReadCloser @@ -25,11 +27,13 @@ func (b *BufferReader) Loop() { defer b.ReleaseWaitGroup() defer func() { recover() }() // ignore errors defer func() { close(b.inputReadyCh) }() // Make sure to close notifier + defer b.input.Close() ch := make(chan string, 10) - // scanner.Scan() blocks until the next read or error. But we want to - // exit immediately, so we move it out to its own goroutine + // scanner.Scan() blocks until the next read or error. But we want our + // main loop to be able to exit without blocking, so we move this out + // to its own goroutine go func() { defer func() { recover() }() defer func() { close(ch) }() @@ -43,8 +47,27 @@ func (b *BufferReader) Loop() { once := &sync.Once{} var refresh *time.Timer - loop := true - for loop { + doDelayedDraw := func() { + m.Lock() + defer m.Unlock() + + trace("doDelayedDraw") + + if refresh != nil { + return + } + + refresh = time.AfterFunc(100*time.Millisecond, func() { + if !b.ExecQuery() { + b.SendDraw() + } + m.Lock() + defer m.Unlock() + refresh = nil + }) + } + + for loop := true; loop; { select { case <-b.LoopCh(): loop = false @@ -56,6 +79,7 @@ func (b *BufferReader) Loop() { if line != "" { // Notify once that we have received something from the file/stdin + // This is the cue to start initializing the terminal once.Do(func() { b.inputReadyCh <- struct{}{} }) // Make sure we lock access to b.lines @@ -64,23 +88,10 @@ func (b *BufferReader) Loop() { m.Unlock() } - m.Lock() - if refresh == nil { - refresh = time.AfterFunc(100*time.Millisecond, func() { - if !b.ExecQuery() { - b.SendDraw() - } - m.Lock() - refresh = nil - m.Unlock() - }) - } - m.Unlock() + doDelayedDraw() } } - b.input.Close() - // Out of the reader loop. If at this point we have no buffer, // that means we have no buffer, so we should quit. if b.GetRawLineBufferSize() == 0 {