Always listen for SIGINT

This commit is contained in:
Christian Rocha 2021-01-11 16:33:14 -05:00
parent 64da3bcf7a
commit 9f04c936da
2 changed files with 63 additions and 22 deletions

54
tea.go
View File

@ -10,11 +10,14 @@
package tea package tea
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/signal"
"runtime/debug" "runtime/debug"
"sync" "sync"
"syscall"
isatty "github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
te "github.com/muesli/termenv" te "github.com/muesli/termenv"
@ -109,6 +112,9 @@ type Program struct {
// from panics, print the stack trace, and disable raw mode. This feature // from panics, print the stack trace, and disable raw mode. This feature
// is on by default. // is on by default.
CatchPanics bool CatchPanics bool
inputIsTTY bool
outputIsTTY bool
} }
// Quit is a special command that tells the Bubble Tea program to exit. // Quit is a special command that tells the Bubble Tea program to exit.
@ -167,17 +173,43 @@ func (p *Program) Start() error {
msgs = make(chan Msg) msgs = make(chan Msg)
errs = make(chan error) errs = make(chan error)
done = make(chan struct{}) 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
// rather than do a nil check against the value here.
outputAsFile *os.File
) )
// Is output a file? ctx, cancel := context.WithCancel(context.Background())
outputAsFile, outputIsFile := p.output.(*os.File) defer cancel()
// Is output a TTY? // Is output a terminal?
var isTTY bool if f, ok := p.output.(*os.File); ok {
if outputIsFile { outputAsFile = f
isTTY = isatty.IsTerminal(outputAsFile.Fd()) p.outputIsTTY = isatty.IsTerminal(f.Fd())
} }
// Is input a terminal?
if f, ok := p.input.(*os.File); ok {
p.inputIsTTY = isatty.IsTerminal(f.Fd())
}
// Listen for SIGINT. Note that in most cases ^C will not send an
// interrupt because the terminal will be in raw mode and thus capture
// that keystroke and send it along to Program.Update. If input is not a
// TTY, however, ^C will be caught here.
go func() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT)
defer signal.Stop(sig)
select {
case <-ctx.Done():
case <-sig:
msgs <- quitMsg{}
}
}()
if p.CatchPanics { if p.CatchPanics {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -193,12 +225,12 @@ func (p *Program) Start() error {
// Check if output is a TTY before entering raw mode, hiding the cursor and // Check if output is a TTY before entering raw mode, hiding the cursor and
// so on. // so on.
if isTTY { {
err := initTerminal(p.output) err := p.initTerminal()
if err != nil { if err != nil {
return err return err
} }
defer restoreTerminal(p.output) defer p.restoreTerminal()
} }
// Initialize program // Initialize program
@ -218,7 +250,7 @@ func (p *Program) Start() error {
p.renderer.write(model.View()) p.renderer.write(model.View())
// Subscribe to user input // Subscribe to user input
if p.input != nil { if p.inputIsTTY {
go func() { go func() {
for { for {
msg, err := readInput(p.input) msg, err := readInput(p.input)
@ -234,7 +266,7 @@ func (p *Program) Start() error {
}() }()
} }
if isTTY { if p.outputIsTTY {
// Get initial terminal size // Get initial terminal size
go func() { go func() {
w, h, err := terminal.GetSize(int(outputAsFile.Fd())) w, h, err := terminal.GetSize(int(outputAsFile.Fd()))

31
tty.go
View File

@ -1,26 +1,35 @@
package tea package tea
import ( import (
"io"
"github.com/containerd/console" "github.com/containerd/console"
) )
var tty console.Console var tty console.Console
func initTerminal(w io.Writer) error { func (p Program) initTerminal() error {
tty = console.Current() if p.outputIsTTY {
err := tty.SetRaw() tty = console.Current()
if err != nil { }
return err
if p.inputIsTTY {
err := tty.SetRaw()
if err != nil {
return err
}
}
if p.outputIsTTY {
enableAnsiColors(p.output)
hideCursor(p.output)
} }
enableAnsiColors(w)
hideCursor(w)
return nil return nil
} }
func restoreTerminal(w io.Writer) error { func (p Program) restoreTerminal() error {
showCursor(w) if !p.outputIsTTY {
return nil
}
showCursor(p.output)
return tty.Reset() return tty.Reset()
} }