fix: kill should result in Start returning an error

This fixes Kill resulting in a final nil model being returned.

We can also drop the kill channel and rely on our existing context
channel.
This commit is contained in:
Christian Muehlhaeuser 2022-10-07 23:20:58 +02:00
parent fd18c149df
commit 1ed623fdc0
2 changed files with 20 additions and 15 deletions

31
tea.go
View File

@ -11,6 +11,7 @@ package tea
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -26,6 +27,9 @@ import (
"golang.org/x/term" "golang.org/x/term"
) )
// ErrProgramKilled is returned by [Program.Run] when the program got killed.
var ErrProgramKilled = errors.New("program was killed")
// Msg contain data from the result of a IO operation. Msgs trigger the update // Msg contain data from the result of a IO operation. Msgs trigger the update
// function and, henceforth, the UI. // function and, henceforth, the UI.
type Msg interface{} type Msg interface{}
@ -90,7 +94,8 @@ type Program struct {
// treated as bits. These options can be set via various ProgramOptions. // treated as bits. These options can be set via various ProgramOptions.
startupOptions startupOptions startupOptions startupOptions
ctx context.Context ctx context.Context
cancel context.CancelFunc
msgs chan Msg msgs chan Msg
errs chan error errs chan error
@ -110,8 +115,6 @@ type Program struct {
altScreenWasActive bool altScreenWasActive bool
ignoreSignals bool ignoreSignals bool
killc chan bool
// Stores the original reference to stdin for cases where input is not a // Stores the original reference to stdin for cases where input is not a
// TTY on windows and we've automatically opened CONIN$ to receive input. // TTY on windows and we've automatically opened CONIN$ to receive input.
// When the program exits this will be restored. // When the program exits this will be restored.
@ -137,7 +140,6 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
initialModel: model, initialModel: model,
input: os.Stdin, input: os.Stdin,
msgs: make(chan Msg), msgs: make(chan Msg),
killc: make(chan bool, 1),
} }
// Apply all options to the program. // Apply all options to the program.
@ -262,8 +264,8 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
for { for {
select { select {
case <-p.killc: case <-p.ctx.Done():
return nil, nil return model, nil
case err := <-p.errs: case err := <-p.errs:
return model, err return model, err
@ -342,9 +344,8 @@ func (p *Program) Run() (Model, error) {
cmds := make(chan Cmd) cmds := make(chan Cmd)
p.errs = make(chan error) p.errs = make(chan error)
var cancelContext context.CancelFunc p.ctx, p.cancel = context.WithCancel(context.Background())
p.ctx, cancelContext = context.WithCancel(context.Background()) defer p.cancel()
defer cancelContext()
switch { switch {
case p.startupOptions.has(withInputTTY): case p.startupOptions.has(withInputTTY):
@ -455,9 +456,13 @@ func (p *Program) Run() (Model, error) {
// 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)
killed := p.ctx.Err() != nil
if killed {
err = ErrProgramKilled
}
// Tear down. // Tear down.
cancelContext() p.cancel()
// Wait for input loop to finish. // Wait for input loop to finish.
if p.cancelReader.Cancel() { if p.cancelReader.Cancel() {
@ -468,7 +473,7 @@ func (p *Program) Run() (Model, error) {
handlers.shutdown() handlers.shutdown()
// Restore terminal state. // Restore terminal state.
p.shutdown(false) p.shutdown(killed)
return model, err return model, err
} }
@ -515,9 +520,9 @@ func (p *Program) Quit() {
// Kill stops the program immediately and restores the former terminal state. // Kill stops the program immediately and restores the former terminal state.
// The final render that you would normally see when quitting will be skipped. // The final render that you would normally see when quitting will be skipped.
// [program.Run] returns a [ErrProgramKilled] error.
func (p *Program) Kill() { func (p *Program) Kill() {
p.killc <- true p.cancel()
p.shutdown(true)
} }
// shutdown performs operations to free up resources and restore the terminal // shutdown performs operations to free up resources and restore the terminal

View File

@ -92,8 +92,8 @@ func TestTeaKill(t *testing.T) {
} }
}() }()
if _, err := p.Run(); err != nil { if _, err := p.Run(); err != ErrProgramKilled {
t.Fatal(err) t.Fatalf("Expected %v, got %v", ErrProgramKilled, err)
} }
} }