forked from toolshed/abra
chore: bump deps
This commit is contained in:
119
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
119
vendor/github.com/charmbracelet/bubbletea/tea.go
generated
vendored
@ -27,6 +27,9 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic.
|
||||
var ErrProgramPanic = errors.New("program experienced a panic")
|
||||
|
||||
// ErrProgramKilled is returned by [Program.Run] when the program gets killed.
|
||||
var ErrProgramKilled = errors.New("program was killed")
|
||||
|
||||
@ -147,6 +150,12 @@ type Program struct {
|
||||
|
||||
inputType inputType
|
||||
|
||||
// externalCtx is a context that was passed in via WithContext, otherwise defaulting
|
||||
// to ctx.Background() (in case it was not), the internal context is derived from it.
|
||||
externalCtx context.Context
|
||||
|
||||
// ctx is the programs's internal context for signalling internal teardown.
|
||||
// It is built and derived from the externalCtx in NewProgram().
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
@ -243,11 +252,11 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||
|
||||
// A context can be provided with a ProgramOption, but if none was provided
|
||||
// we'll use the default background context.
|
||||
if p.ctx == nil {
|
||||
p.ctx = context.Background()
|
||||
if p.externalCtx == nil {
|
||||
p.externalCtx = context.Background()
|
||||
}
|
||||
// Initialize context and teardown channel.
|
||||
p.ctx, p.cancel = context.WithCancel(p.ctx)
|
||||
p.ctx, p.cancel = context.WithCancel(p.externalCtx)
|
||||
|
||||
// if no output was set, set it to stdout
|
||||
if p.output == nil {
|
||||
@ -346,7 +355,11 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
|
||||
go func() {
|
||||
// Recover from panics.
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer p.recoverFromPanic()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
p.recoverFromGoPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
msg := cmd() // this can be long.
|
||||
@ -422,7 +435,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// work.
|
||||
if runtime.GOOS == "windows" && !p.mouseMode {
|
||||
p.mouseMode = true
|
||||
p.initCancelReader(true) //nolint:errcheck
|
||||
p.initCancelReader(true) //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
case disableMouseMsg:
|
||||
@ -433,7 +446,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// mouse events.
|
||||
if runtime.GOOS == "windows" && p.mouseMode {
|
||||
p.mouseMode = false
|
||||
p.initCancelReader(true) //nolint:errcheck
|
||||
p.initCancelReader(true) //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
case showCursorMsg:
|
||||
@ -460,7 +473,11 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
|
||||
case BatchMsg:
|
||||
for _, cmd := range msg {
|
||||
cmds <- cmd
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return model, nil
|
||||
case cmds <- cmd:
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
||||
@ -483,7 +500,7 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
//nolint:errcheck,gosec
|
||||
g.Wait() // wait for all commands from batch msg to finish
|
||||
continue
|
||||
}
|
||||
@ -506,7 +523,13 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
|
||||
var cmd Cmd
|
||||
model, cmd = model.Update(msg) // run update
|
||||
cmds <- cmd // process command (if any)
|
||||
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return model, nil
|
||||
case cmds <- cmd: // process command (if any)
|
||||
}
|
||||
|
||||
p.renderer.write(model.View()) // send view to renderer
|
||||
}
|
||||
}
|
||||
@ -515,11 +538,15 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||
// Run initializes the program and runs its event loops, blocking until it gets
|
||||
// terminated by either [Program.Quit], [Program.Kill], or its signal handler.
|
||||
// Returns the final model.
|
||||
func (p *Program) Run() (Model, error) {
|
||||
func (p *Program) Run() (returnModel Model, returnErr error) {
|
||||
p.handlers = channelHandlers{}
|
||||
cmds := make(chan Cmd)
|
||||
p.errs = make(chan error)
|
||||
p.finished = make(chan struct{}, 1)
|
||||
p.errs = make(chan error, 1)
|
||||
|
||||
p.finished = make(chan struct{})
|
||||
defer func() {
|
||||
close(p.finished)
|
||||
}()
|
||||
|
||||
defer p.cancel()
|
||||
|
||||
@ -568,7 +595,12 @@ func (p *Program) Run() (Model, error) {
|
||||
|
||||
// Recover from panics.
|
||||
if !p.startupOptions.has(withoutCatchPanics) {
|
||||
defer p.recoverFromPanic()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
returnErr = fmt.Errorf("%w: %w", ErrProgramKilled, ErrProgramPanic)
|
||||
p.recoverFromPanic(r)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// If no renderer is set use the standard one.
|
||||
@ -645,11 +677,27 @@ func (p *Program) Run() (Model, error) {
|
||||
|
||||
// Run event loop, handle updates and draw.
|
||||
model, err := p.eventLoop(model, cmds)
|
||||
killed := p.ctx.Err() != nil || err != nil
|
||||
if killed && err == nil {
|
||||
err = fmt.Errorf("%w: %s", ErrProgramKilled, p.ctx.Err())
|
||||
|
||||
if err == nil && len(p.errs) > 0 {
|
||||
err = <-p.errs // Drain a leftover error in case eventLoop crashed
|
||||
}
|
||||
if err == nil {
|
||||
|
||||
killed := p.externalCtx.Err() != nil || p.ctx.Err() != nil || err != nil
|
||||
if killed {
|
||||
if err == nil && p.externalCtx.Err() != nil {
|
||||
// Return also as context error the cancellation of an external context.
|
||||
// This is the context the user knows about and should be able to act on.
|
||||
err = fmt.Errorf("%w: %w", ErrProgramKilled, p.externalCtx.Err())
|
||||
} else if err == nil && p.ctx.Err() != nil {
|
||||
// Return only that the program was killed (not the internal mechanism).
|
||||
// The user does not know or need to care about the internal program context.
|
||||
err = ErrProgramKilled
|
||||
} else {
|
||||
// Return that the program was killed and also the error that caused it.
|
||||
err = fmt.Errorf("%w: %w", ErrProgramKilled, err)
|
||||
}
|
||||
} else {
|
||||
// Graceful shutdown of the program (not killed):
|
||||
// Ensure we rendered the final state of the model.
|
||||
p.renderer.write(model.View())
|
||||
}
|
||||
@ -704,11 +752,11 @@ func (p *Program) Quit() {
|
||||
p.Send(Quit())
|
||||
}
|
||||
|
||||
// Kill stops the program immediately and restores the former terminal state.
|
||||
// Kill signals the program to stop immediately and restore the former terminal state.
|
||||
// The final render that you would normally see when quitting will be skipped.
|
||||
// [program.Run] returns a [ErrProgramKilled] error.
|
||||
func (p *Program) Kill() {
|
||||
p.shutdown(true)
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
// Wait waits/blocks until the underlying Program finished shutting down.
|
||||
@ -717,7 +765,11 @@ func (p *Program) Wait() {
|
||||
}
|
||||
|
||||
// shutdown performs operations to free up resources and restore the terminal
|
||||
// to its original state.
|
||||
// to its original state. It is called once at the end of the program's lifetime.
|
||||
//
|
||||
// This method should not be called to signal the program to be killed/shutdown.
|
||||
// Doing so can lead to race conditions with the eventual call at the program's end.
|
||||
// As alternatives, the [Quit] or [Kill] convenience methods should be used instead.
|
||||
func (p *Program) shutdown(kill bool) {
|
||||
p.cancel()
|
||||
|
||||
@ -744,19 +796,30 @@ func (p *Program) shutdown(kill bool) {
|
||||
}
|
||||
|
||||
_ = p.restoreTerminalState()
|
||||
if !kill {
|
||||
p.finished <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// recoverFromPanic recovers from a panic, prints the stack trace, and restores
|
||||
// the terminal to a usable state.
|
||||
func (p *Program) recoverFromPanic() {
|
||||
if r := recover(); r != nil {
|
||||
p.shutdown(true)
|
||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
func (p *Program) recoverFromPanic(r interface{}) {
|
||||
select {
|
||||
case p.errs <- ErrProgramPanic:
|
||||
default:
|
||||
}
|
||||
p.shutdown(true) // Ok to call here, p.Run() cannot do it anymore.
|
||||
fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
// recoverFromGoPanic recovers from a goroutine panic, prints a stack trace and
|
||||
// signals for the program to be killed and terminal restored to a usable state.
|
||||
func (p *Program) recoverFromGoPanic(r interface{}) {
|
||||
select {
|
||||
case p.errs <- ErrProgramPanic:
|
||||
default:
|
||||
}
|
||||
p.cancel()
|
||||
fmt.Printf("Caught goroutine panic:\n\n%s\n\nRestoring terminal...\n\n", r)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
// ReleaseTerminal restores the original terminal state and cancels the input
|
||||
|
Reference in New Issue
Block a user