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

29
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{}
@ -91,6 +95,7 @@ type Program struct {
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)
} }
} }