forked from Mirrors/bubbletea
chore: store handlers and simplify teardown
This commit is contained in:
parent
76ce669474
commit
6477a53545
57
tea.go
57
tea.go
|
@ -16,6 +16,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -54,6 +55,8 @@ type Model interface {
|
||||||
// update function.
|
// update function.
|
||||||
type Cmd func() Msg
|
type Cmd func() Msg
|
||||||
|
|
||||||
|
type handlers []chan struct{}
|
||||||
|
|
||||||
// Options to customize the program during its initialization. These are
|
// Options to customize the program during its initialization. These are
|
||||||
// generally set with ProgramOptions.
|
// generally set with ProgramOptions.
|
||||||
//
|
//
|
||||||
|
@ -178,6 +181,7 @@ func (p *Program) handleSignals() chan struct{} {
|
||||||
select {
|
select {
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-sig:
|
case <-sig:
|
||||||
if !p.ignoreSignals {
|
if !p.ignoreSignals {
|
||||||
p.msgs <- quitMsg{}
|
p.msgs <- quitMsg{}
|
||||||
|
@ -332,6 +336,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||||
|
|
||||||
// StartReturningModel initializes the program. Returns the final model.
|
// StartReturningModel initializes the program. Returns the final model.
|
||||||
func (p *Program) StartReturningModel() (Model, error) {
|
func (p *Program) StartReturningModel() (Model, error) {
|
||||||
|
handlers := handlers{}
|
||||||
cmds := make(chan Cmd)
|
cmds := make(chan Cmd)
|
||||||
p.errs = make(chan error)
|
p.errs = make(chan error)
|
||||||
|
|
||||||
|
@ -360,7 +365,6 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
if !isFile {
|
if !isFile {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if isatty.IsTerminal(f.Fd()) {
|
if isatty.IsTerminal(f.Fd()) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -376,11 +380,8 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle signals.
|
// Handle signals.
|
||||||
sigintLoopDone := make(chan struct{})
|
|
||||||
if !p.startupOptions.has(withoutSignalHandler) {
|
if !p.startupOptions.has(withoutSignalHandler) {
|
||||||
sigintLoopDone = p.handleSignals()
|
handlers.add(p.handleSignals())
|
||||||
} else {
|
|
||||||
close(sigintLoopDone)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recover from panics.
|
// Recover from panics.
|
||||||
|
@ -417,18 +418,19 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the program.
|
// Initialize the program.
|
||||||
initSignalDone := make(chan struct{})
|
|
||||||
model := p.initialModel
|
model := p.initialModel
|
||||||
if initCmd := model.Init(); initCmd != nil {
|
if initCmd := model.Init(); initCmd != nil {
|
||||||
|
ch := make(chan struct{})
|
||||||
|
handlers.add(ch)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(initSignalDone)
|
defer close(ch)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case cmds <- initCmd:
|
case cmds <- initCmd:
|
||||||
case <-p.ctx.Done():
|
case <-p.ctx.Done():
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
|
||||||
close(initSignalDone)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the renderer.
|
// Start the renderer.
|
||||||
|
@ -442,16 +444,15 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
if err := p.initCancelReader(); err != nil {
|
if err := p.initCancelReader(); err != nil {
|
||||||
return model, err
|
return model, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
defer close(p.readLoopDone)
|
|
||||||
}
|
|
||||||
defer p.cancelReader.Close() //nolint:errcheck
|
defer p.cancelReader.Close() //nolint:errcheck
|
||||||
|
handlers.add(p.readLoopDone)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle resize events.
|
// Handle resize events.
|
||||||
resizeLoopDone := p.handleResize()
|
handlers.add(p.handleResize())
|
||||||
|
|
||||||
// Process commands.
|
// Process commands.
|
||||||
cmdLoopDone := p.handleCommands(cmds)
|
handlers.add(p.handleCommands(cmds))
|
||||||
|
|
||||||
// Run event loop, handle updates and draw.
|
// Run event loop, handle updates and draw.
|
||||||
model, err := p.eventLoop(model, cmds)
|
model, err := p.eventLoop(model, cmds)
|
||||||
|
@ -463,10 +464,11 @@ func (p *Program) StartReturningModel() (Model, error) {
|
||||||
if p.cancelReader.Cancel() {
|
if p.cancelReader.Cancel() {
|
||||||
p.waitForReadLoop()
|
p.waitForReadLoop()
|
||||||
}
|
}
|
||||||
<-cmdLoopDone
|
|
||||||
<-resizeLoopDone
|
// Wait for all handlers to finish.
|
||||||
<-sigintLoopDone
|
handlers.shutdown()
|
||||||
<-initSignalDone
|
|
||||||
|
// Restore terminal state.
|
||||||
p.shutdown(false)
|
p.shutdown(false)
|
||||||
|
|
||||||
return model, err
|
return model, err
|
||||||
|
@ -587,3 +589,22 @@ func (p *Program) Printf(template string, args ...interface{}) {
|
||||||
messageBody: fmt.Sprintf(template, args...),
|
messageBody: fmt.Sprintf(template, args...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds a handler to the list of handlers. We wait for all handlers to terminate
|
||||||
|
// gracefully on shutdown.
|
||||||
|
func (h *handlers) add(ch chan struct{}) {
|
||||||
|
*h = append(*h, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown waits for all handlers to terminate.
|
||||||
|
func (h handlers) shutdown() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, ch := range h {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(ch chan struct{}) {
|
||||||
|
<-ch
|
||||||
|
wg.Done()
|
||||||
|
}(ch)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue