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{
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()
}
}
}

7
tea.go
View File

@ -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