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.
This commit is contained in:
Christian Muehlhaeuser 2023-03-06 14:39:55 +01:00
parent 90c9124b0a
commit 402d2b4e2b
2 changed files with 17 additions and 8 deletions

View File

@ -58,6 +58,7 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer {
r := &standardRenderer{ r := &standardRenderer{
out: out, out: out,
mtx: &sync.Mutex{}, mtx: &sync.Mutex{},
done: make(chan struct{}),
framerate: defaultFramerate, framerate: defaultFramerate,
useANSICompressor: useANSICompressor, useANSICompressor: useANSICompressor,
queuedMessageLines: []string{}, queuedMessageLines: []string{},
@ -72,12 +73,15 @@ func newRenderer(out *termenv.Output, useANSICompressor bool) renderer {
func (r *standardRenderer) start() { func (r *standardRenderer) start() {
if r.ticker == nil { if r.ticker == nil {
r.ticker = time.NewTicker(r.framerate) 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 // Since the renderer can be restarted after a stop, we need to reset
// the done channel and its corresponding sync.Once. // the done channel and its corresponding sync.Once.
r.once = sync.Once{} r.once = sync.Once{}
r.done = make(chan struct{})
go r.listen() go r.listen()
} }
@ -92,7 +96,7 @@ func (r *standardRenderer) stop() {
r.out.ClearLine() r.out.ClearLine()
r.once.Do(func() { r.once.Do(func() {
close(r.done) r.done <- struct{}{}
}) })
if r.useANSICompressor { if r.useANSICompressor {
@ -109,7 +113,7 @@ func (r *standardRenderer) kill() {
r.out.ClearLine() r.out.ClearLine()
r.once.Do(func() { r.once.Do(func() {
close(r.done) r.done <- struct{}{}
}) })
} }
@ -117,14 +121,12 @@ func (r *standardRenderer) kill() {
func (r *standardRenderer) listen() { func (r *standardRenderer) listen() {
for { for {
select { select {
case <-r.ticker.C:
if r.ticker != nil {
r.flush()
}
case <-r.done: case <-r.done:
r.ticker.Stop() r.ticker.Stop()
r.ticker = nil
return return
case <-r.ticker.C:
r.flush()
} }
} }
} }

7
tea.go
View File

@ -567,6 +567,10 @@ func (p *Program) ReleaseTerminal() error {
p.cancelReader.Cancel() p.cancelReader.Cancel()
p.waitForReadLoop() p.waitForReadLoop()
if p.renderer != nil {
p.renderer.stop()
}
p.altScreenWasActive = p.renderer.altScreen() p.altScreenWasActive = p.renderer.altScreen()
return p.restoreTerminalState() return p.restoreTerminalState()
} }
@ -590,6 +594,9 @@ func (p *Program) RestoreTerminal() error {
// entering alt screen already causes a repaint. // entering alt screen already causes a repaint.
go p.Send(repaintMsg{}) go p.Send(repaintMsg{})
} }
if p.renderer != nil {
p.renderer.start()
}
// If the output is a terminal, it may have been resized while another // 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 // process was at the foreground, in which case we may not have received