From 42cd4c31919c2bfc8c06761634dedffcae9ca369 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 4 Feb 2022 10:09:17 -0500 Subject: [PATCH] feat: kill the program and restore terminal state * Add Kill() to force kill the program from the outside * Prevent renderer channel from being closed multiple times --- standard_renderer.go | 9 +++++++-- tea.go | 25 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/standard_renderer.go b/standard_renderer.go index 765cf91..33d14a7 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -32,6 +32,7 @@ type standardRenderer struct { lastRender string linesRendered int useANSICompressor bool + once sync.Once // essentially whether or not we're using the full size of the terminal altScreenActive bool @@ -72,7 +73,9 @@ func (r *standardRenderer) start() { func (r *standardRenderer) stop() { r.flush() clearLine(r.out) - close(r.done) + r.once.Do(func() { + close(r.done) + }) if r.useANSICompressor { if w, ok := r.out.(io.WriteCloser); ok { @@ -84,7 +87,9 @@ func (r *standardRenderer) stop() { // kill halts the renderer. The final frame will not be rendered. func (r *standardRenderer) kill() { clearLine(r.out) - close(r.done) + r.once.Do(func() { + close(r.done) + }) } // listen waits for ticks on the ticker, or a signal to stop the renderer. diff --git a/tea.go b/tea.go index ee1bd92..3baaf5b 100644 --- a/tea.go +++ b/tea.go @@ -97,6 +97,8 @@ type Program struct { // is on by default. CatchPanics bool + killc chan bool + console console.Console // Stores the original reference to stdin for cases where input is not a @@ -248,6 +250,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { input: os.Stdin, msgs: make(chan Msg), CatchPanics: true, + killc: make(chan bool, 1), } // Apply all options to the program. @@ -486,6 +489,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,13 +582,25 @@ func (p *Program) Quit() { p.Send(Quit()) } +// Kill stops the program immediately and restores the former terminal state. +// The final render that 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) { - if kill { - p.renderer.kill() - } else { - p.renderer.stop() + if p.renderer != nil { + if kill { + p.renderer.kill() + } else { + p.renderer.stop() + } } p.ExitAltScreen() p.DisableMouseCellMotion()