forked from Mirrors/bubbletea
Always listen for SIGINT
This commit is contained in:
parent
64da3bcf7a
commit
9f04c936da
54
tea.go
54
tea.go
|
@ -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
31
tty.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue