forked from Mirrors/bubbletea
Fix a race where artifacts could print when exiting a program
This commit also consolidates the exit operations for consistency's sake, and adds a kill() method to renderers for stopping them without performing any final rendering.
This commit is contained in:
parent
85ab476698
commit
1f773e8f20
|
@ -4,6 +4,7 @@ type nilRenderer struct{}
|
|||
|
||||
func (n nilRenderer) start() {}
|
||||
func (n nilRenderer) stop() {}
|
||||
func (n nilRenderer) kill() {}
|
||||
func (n nilRenderer) write(v string) {}
|
||||
func (n nilRenderer) repaint() {}
|
||||
func (n nilRenderer) altScreen() bool { return false }
|
||||
|
|
16
renderer.go
16
renderer.go
|
@ -2,10 +2,26 @@ package tea
|
|||
|
||||
// renderer is the interface for Bubble Tea renderers.
|
||||
type renderer interface {
|
||||
// Start the renderer.
|
||||
start()
|
||||
|
||||
// Stop the renderer, but render the final frame in the buffer, if any.
|
||||
stop()
|
||||
|
||||
// Stop the renderer without doing any final rendering.
|
||||
kill()
|
||||
|
||||
// Write a frame to the renderer. The renderer can write this data to ouput
|
||||
// at its discretion.
|
||||
write(string)
|
||||
|
||||
// Request a full re-render.
|
||||
repaint()
|
||||
|
||||
// Whether or not the alternate screen buffer is enabled.
|
||||
altScreen() bool
|
||||
|
||||
// Record internally that the alternate screen buffer is enabled. This does
|
||||
// should not actually toggle the alternate screen buffer.
|
||||
setAltScreen(bool)
|
||||
}
|
||||
|
|
|
@ -61,13 +61,19 @@ func (r *standardRenderer) start() {
|
|||
go r.listen()
|
||||
}
|
||||
|
||||
// stop permanently halts the renderer.
|
||||
// stop permanently halts the renderer, rendering the final frame.
|
||||
func (r *standardRenderer) stop() {
|
||||
r.flush()
|
||||
clearLine(r.out)
|
||||
close(r.done)
|
||||
}
|
||||
|
||||
// kill halts the renderer. The final frame will not be rendered.
|
||||
func (r *standardRenderer) kill() {
|
||||
clearLine(r.out)
|
||||
close(r.done)
|
||||
}
|
||||
|
||||
// listen waits for ticks on the ticker, or a signal to stop the renderer.
|
||||
func (r *standardRenderer) listen() {
|
||||
for {
|
||||
|
|
35
tea.go
35
tea.go
|
@ -213,6 +213,7 @@ type Program struct {
|
|||
startupOptions startupOptions
|
||||
|
||||
mtx *sync.Mutex
|
||||
done chan struct{}
|
||||
|
||||
output io.Writer // where to send output. this will usually be os.Stdout.
|
||||
input io.Reader // this will usually be os.Stdin.
|
||||
|
@ -370,6 +371,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
|||
initialModel: model,
|
||||
output: os.Stdout,
|
||||
input: os.Stdin,
|
||||
done: make(chan struct{}),
|
||||
CatchPanics: true,
|
||||
}
|
||||
|
||||
|
@ -387,7 +389,6 @@ func (p *Program) Start() error {
|
|||
cmds = make(chan Cmd)
|
||||
msgs = make(chan Msg)
|
||||
errs = make(chan error)
|
||||
done = make(chan struct{})
|
||||
|
||||
// If output is a file (e.g. os.Stdout) then this will be set
|
||||
// accordingly. Most of the time you should refer to p.outputIsTTY
|
||||
|
@ -441,9 +442,7 @@ func (p *Program) Start() error {
|
|||
if p.CatchPanics {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.ExitAltScreen()
|
||||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
p.shutdown(true)
|
||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
return
|
||||
|
@ -458,9 +457,6 @@ func (p *Program) Start() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = p.restoreTerminal()
|
||||
}()
|
||||
}
|
||||
|
||||
// If no renderer is set use the standard one.
|
||||
|
@ -529,7 +525,7 @@ func (p *Program) Start() error {
|
|||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
case <-p.done:
|
||||
return
|
||||
case cmd := <-cmds:
|
||||
if cmd != nil {
|
||||
|
@ -545,18 +541,14 @@ func (p *Program) Start() error {
|
|||
for {
|
||||
select {
|
||||
case err := <-errs:
|
||||
close(done)
|
||||
p.shutdown(false)
|
||||
return err
|
||||
case msg := <-msgs:
|
||||
|
||||
// Handle special internal messages
|
||||
switch msg := msg.(type) {
|
||||
case quitMsg:
|
||||
p.ExitAltScreen()
|
||||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
p.renderer.stop()
|
||||
close(done)
|
||||
p.shutdown(false)
|
||||
return nil
|
||||
|
||||
case batchMsg:
|
||||
|
@ -601,6 +593,21 @@ func (p *Program) Start() error {
|
|||
}
|
||||
}
|
||||
|
||||
// shutdown performs operations to free up resources and restore the terminal
|
||||
// to its original state.
|
||||
func (p *Program) shutdown(kill bool) {
|
||||
if kill {
|
||||
p.renderer.kill()
|
||||
} else {
|
||||
p.renderer.stop()
|
||||
}
|
||||
close(p.done)
|
||||
p.ExitAltScreen()
|
||||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
_ = p.restoreTerminal()
|
||||
}
|
||||
|
||||
// EnterAltScreen enters the alternate screen buffer, which consumes the entire
|
||||
// terminal window. ExitAltScreen will return the terminal to its former state.
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue