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) start() {}
|
||||||
func (n nilRenderer) stop() {}
|
func (n nilRenderer) stop() {}
|
||||||
|
func (n nilRenderer) kill() {}
|
||||||
func (n nilRenderer) write(v string) {}
|
func (n nilRenderer) write(v string) {}
|
||||||
func (n nilRenderer) repaint() {}
|
func (n nilRenderer) repaint() {}
|
||||||
func (n nilRenderer) altScreen() bool { return false }
|
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.
|
// renderer is the interface for Bubble Tea renderers.
|
||||||
type renderer interface {
|
type renderer interface {
|
||||||
|
// Start the renderer.
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
// Stop the renderer, but render the final frame in the buffer, if any.
|
||||||
stop()
|
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)
|
write(string)
|
||||||
|
|
||||||
|
// Request a full re-render.
|
||||||
repaint()
|
repaint()
|
||||||
|
|
||||||
|
// Whether or not the alternate screen buffer is enabled.
|
||||||
altScreen() bool
|
altScreen() bool
|
||||||
|
|
||||||
|
// Record internally that the alternate screen buffer is enabled. This does
|
||||||
|
// should not actually toggle the alternate screen buffer.
|
||||||
setAltScreen(bool)
|
setAltScreen(bool)
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,13 +61,19 @@ func (r *standardRenderer) start() {
|
||||||
go r.listen()
|
go r.listen()
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop permanently halts the renderer.
|
// stop permanently halts the renderer, rendering the final frame.
|
||||||
func (r *standardRenderer) stop() {
|
func (r *standardRenderer) stop() {
|
||||||
r.flush()
|
r.flush()
|
||||||
clearLine(r.out)
|
clearLine(r.out)
|
||||||
close(r.done)
|
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.
|
// listen waits for ticks on the ticker, or a signal to stop the renderer.
|
||||||
func (r *standardRenderer) listen() {
|
func (r *standardRenderer) listen() {
|
||||||
for {
|
for {
|
||||||
|
|
37
tea.go
37
tea.go
|
@ -212,7 +212,8 @@ type Program struct {
|
||||||
// treated as bits. These options can be set via various ProgramOptions.
|
// treated as bits. These options can be set via various ProgramOptions.
|
||||||
startupOptions startupOptions
|
startupOptions startupOptions
|
||||||
|
|
||||||
mtx *sync.Mutex
|
mtx *sync.Mutex
|
||||||
|
done chan struct{}
|
||||||
|
|
||||||
output io.Writer // where to send output. this will usually be os.Stdout.
|
output io.Writer // where to send output. this will usually be os.Stdout.
|
||||||
input io.Reader // this will usually be os.Stdin.
|
input io.Reader // this will usually be os.Stdin.
|
||||||
|
@ -370,6 +371,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||||
initialModel: model,
|
initialModel: model,
|
||||||
output: os.Stdout,
|
output: os.Stdout,
|
||||||
input: os.Stdin,
|
input: os.Stdin,
|
||||||
|
done: make(chan struct{}),
|
||||||
CatchPanics: true,
|
CatchPanics: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +389,6 @@ func (p *Program) Start() error {
|
||||||
cmds = make(chan Cmd)
|
cmds = make(chan Cmd)
|
||||||
msgs = make(chan Msg)
|
msgs = make(chan Msg)
|
||||||
errs = make(chan error)
|
errs = make(chan error)
|
||||||
done = make(chan struct{})
|
|
||||||
|
|
||||||
// If output is a file (e.g. os.Stdout) then this will be set
|
// 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
|
// accordingly. Most of the time you should refer to p.outputIsTTY
|
||||||
|
@ -441,9 +442,7 @@ func (p *Program) Start() error {
|
||||||
if p.CatchPanics {
|
if p.CatchPanics {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
p.ExitAltScreen()
|
p.shutdown(true)
|
||||||
p.DisableMouseCellMotion()
|
|
||||||
p.DisableMouseAllMotion()
|
|
||||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
return
|
return
|
||||||
|
@ -458,9 +457,6 @@ func (p *Program) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
_ = p.restoreTerminal()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no renderer is set use the standard one.
|
// If no renderer is set use the standard one.
|
||||||
|
@ -529,7 +525,7 @@ func (p *Program) Start() error {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-p.done:
|
||||||
return
|
return
|
||||||
case cmd := <-cmds:
|
case cmd := <-cmds:
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
|
@ -545,18 +541,14 @@ func (p *Program) Start() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
close(done)
|
p.shutdown(false)
|
||||||
return err
|
return err
|
||||||
case msg := <-msgs:
|
case msg := <-msgs:
|
||||||
|
|
||||||
// Handle special internal messages
|
// Handle special internal messages
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case quitMsg:
|
case quitMsg:
|
||||||
p.ExitAltScreen()
|
p.shutdown(false)
|
||||||
p.DisableMouseCellMotion()
|
|
||||||
p.DisableMouseAllMotion()
|
|
||||||
p.renderer.stop()
|
|
||||||
close(done)
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case batchMsg:
|
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
|
// EnterAltScreen enters the alternate screen buffer, which consumes the entire
|
||||||
// terminal window. ExitAltScreen will return the terminal to its former state.
|
// terminal window. ExitAltScreen will return the terminal to its former state.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue