diff --git a/nil_renderer.go b/nil_renderer.go index cf9fc7a..03dd949 100644 --- a/nil_renderer.go +++ b/nil_renderer.go @@ -4,6 +4,7 @@ type nilRenderer struct{} func (n nilRenderer) start() {} func (n nilRenderer) stop() {} +func (n nilRenderer) kill() {} func (n nilRenderer) write(v string) {} func (n nilRenderer) repaint() {} func (n nilRenderer) altScreen() bool { return false } diff --git a/renderer.go b/renderer.go index 47e9fb4..1abe76d 100644 --- a/renderer.go +++ b/renderer.go @@ -2,10 +2,26 @@ package tea // renderer is the interface for Bubble Tea renderers. type renderer interface { + // Start the renderer. start() + + // Stop the renderer, but render the final frame in the buffer, if any. 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) + + // Request a full re-render. repaint() + + // Whether or not the alternate screen buffer is enabled. altScreen() bool + + // Record internally that the alternate screen buffer is enabled. This does + // should not actually toggle the alternate screen buffer. setAltScreen(bool) } diff --git a/standard_renderer.go b/standard_renderer.go index a26dfe7..7578524 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -61,13 +61,19 @@ func (r *standardRenderer) start() { go r.listen() } -// stop permanently halts the renderer. +// stop permanently halts the renderer, rendering the final frame. func (r *standardRenderer) stop() { r.flush() clearLine(r.out) 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. func (r *standardRenderer) listen() { for { diff --git a/tea.go b/tea.go index 4ca03c9..6303052 100644 --- a/tea.go +++ b/tea.go @@ -212,7 +212,8 @@ type Program struct { // treated as bits. These options can be set via various ProgramOptions. startupOptions startupOptions - mtx *sync.Mutex + mtx *sync.Mutex + done chan struct{} output io.Writer // where to send output. this will usually be os.Stdout. input io.Reader // this will usually be os.Stdin. @@ -370,6 +371,7 @@ func NewProgram(model Model, opts ...ProgramOption) *Program { initialModel: model, output: os.Stdout, input: os.Stdin, + done: make(chan struct{}), CatchPanics: true, } @@ -387,7 +389,6 @@ func (p *Program) Start() error { cmds = make(chan Cmd) msgs = make(chan Msg) errs = make(chan error) - done = make(chan struct{}) // 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 @@ -441,9 +442,7 @@ func (p *Program) Start() error { if p.CatchPanics { defer func() { if r := recover(); r != nil { - p.ExitAltScreen() - p.DisableMouseCellMotion() - p.DisableMouseAllMotion() + p.shutdown(true) fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r) debug.PrintStack() return @@ -458,9 +457,6 @@ func (p *Program) Start() error { if err != nil { return err } - defer func() { - _ = p.restoreTerminal() - }() } // If no renderer is set use the standard one. @@ -529,7 +525,7 @@ func (p *Program) Start() error { go func() { for { select { - case <-done: + case <-p.done: return case cmd := <-cmds: if cmd != nil { @@ -545,18 +541,14 @@ func (p *Program) Start() error { for { select { case err := <-errs: - close(done) + p.shutdown(false) return err case msg := <-msgs: // Handle special internal messages switch msg := msg.(type) { case quitMsg: - p.ExitAltScreen() - p.DisableMouseCellMotion() - p.DisableMouseAllMotion() - p.renderer.stop() - close(done) + p.shutdown(false) return nil 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 // terminal window. ExitAltScreen will return the terminal to its former state. //