diff --git a/options.go b/options.go index 09e5ecbd73..5eca76dc0a 100644 --- a/options.go +++ b/options.go @@ -130,3 +130,11 @@ func WithANSICompressor() ProgramOption { p.startupOptions |= withANSICompressor } } + +// WithPanicHandler sets the program's panic handler. This won't have any effect +// if the WithoutCatchPanics option was enabled. +func WithPanicHandler(f func(*Program)) ProgramOption { + return func(p *Program) { + p.panicHandler = f + } +} diff --git a/tea.go b/tea.go index ee1bd926d1..56dcc02b29 100644 --- a/tea.go +++ b/tea.go @@ -97,6 +97,9 @@ type Program struct { // is on by default. CatchPanics bool + panicHandler func(*Program) + killc chan bool + console console.Console // Stores the original reference to stdin for cases where input is not a @@ -248,6 +251,8 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { input: os.Stdin, msgs: make(chan Msg), CatchPanics: true, + panicHandler: defaultPanicHandler(), + killc: make(chan bool, 1), } // Apply all options to the program. @@ -349,14 +354,7 @@ func (p *Program) StartReturningModel() (Model, error) { }() if p.CatchPanics { - defer func() { - if r := recover(); r != nil { - p.shutdown(true) - fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r) - debug.PrintStack() - return - } - }() + defer p.panicHandler(p) } // Check if output is a TTY before entering raw mode, hiding the cursor and @@ -486,6 +484,8 @@ func (p *Program) StartReturningModel() (Model, error) { // Handle updates and draw. for { select { + case <-p.killc: + return nil, nil case err := <-errs: cancelContext() waitForGoroutines(cancelReader.Cancel()) @@ -577,6 +577,16 @@ func (p *Program) Quit() { p.Send(Quit()) } +// Kill stops the program immediately and restores the former terminal state. +// This final render than you would normally see when quitting will be skipped. +// +// This method is currently provisional. The method signature may alter +// slightly, or it may be removed in a future version of this package. +func (p *Program) Kill() { + p.killc <- true + p.shutdown(true) +} + // shutdown performs operations to free up resources and restore the terminal // to its original state. func (p *Program) shutdown(kill bool) { @@ -671,3 +681,14 @@ func (p *Program) DisableMouseAllMotion() { defer p.mtx.Unlock() fmt.Fprintf(p.output, te.CSI+te.DisableMouseAllMotionSeq) } + +func defaultPanicHandler() func(*Program) { + return func(p *Program) { + if r := recover(); r != nil { + p.shutdown(true) + fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r) + debug.PrintStack() + return + } + } +}