From 402d2b4e2b4c7b71ed5b0c29bea35c339baf8ad7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 6 Mar 2023 14:39:55 +0100 Subject: [PATCH] fix: stop renderer before launching a child process. Stops the renderer before starting a child process, which prevents the repaint race condition that writes to non-altscreen. --- standard_renderer.go | 18 ++++++++++-------- tea.go | 7 +++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/standard_renderer.go b/standard_renderer.go index edde56a..e4fb61c 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -58,6 +58,7 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer { r := &standardRenderer{ out: out, mtx: &sync.Mutex{}, + done: make(chan struct{}), framerate: defaultFramerate, useANSICompressor: useANSICompressor, queuedMessageLines: []string{}, @@ -72,12 +73,15 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer { func (r *standardRenderer) start() { if r.ticker == nil { r.ticker = time.NewTicker(r.framerate) + } else { + // If the ticker already exists, it has been stopped and we need to + // reset it. + r.ticker.Reset(r.framerate) } // Since the renderer can be restarted after a stop, we need to reset // the done channel and its corresponding sync.Once. r.once = sync.Once{} - r.done = make(chan struct{}) go r.listen() } @@ -92,7 +96,7 @@ func (r *standardRenderer) stop() { r.out.ClearLine() r.once.Do(func() { - close(r.done) + r.done <- struct{}{} }) if r.useANSICompressor { @@ -109,7 +113,7 @@ func (r *standardRenderer) kill() { r.out.ClearLine() r.once.Do(func() { - close(r.done) + r.done <- struct{}{} }) } @@ -117,14 +121,12 @@ func (r *standardRenderer) kill() { func (r *standardRenderer) listen() { for { select { - case <-r.ticker.C: - if r.ticker != nil { - r.flush() - } case <-r.done: r.ticker.Stop() - r.ticker = nil return + + case <-r.ticker.C: + r.flush() } } } diff --git a/tea.go b/tea.go index 0c29a86..9132fff 100644 --- a/tea.go +++ b/tea.go @@ -567,6 +567,10 @@ func (p *Program) ReleaseTerminal() error { p.cancelReader.Cancel() p.waitForReadLoop() + if p.renderer != nil { + p.renderer.stop() + } + p.altScreenWasActive = p.renderer.altScreen() return p.restoreTerminalState() } @@ -590,6 +594,9 @@ func (p *Program) RestoreTerminal() error { // entering alt screen already causes a repaint. go p.Send(repaintMsg{}) } + if p.renderer != nil { + p.renderer.start() + } // If the output is a terminal, it may have been resized while another // process was at the foreground, in which case we may not have received