forked from Mirrors/bubbletea
allow custom input and handle eof for os.input properly
Signed-off-by: Christoph Hartmann <chris@lollyrock.com>
This commit is contained in:
parent
bc06e8d2e0
commit
e84314c622
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containerd/console v1.0.1
|
github.com/containerd/console v1.0.1
|
||||||
|
github.com/mattn/go-isatty v0.0.12
|
||||||
github.com/muesli/termenv v0.7.2
|
github.com/muesli/termenv v0.7.2
|
||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
|
||||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634
|
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634
|
||||||
|
|
70
tea.go
70
tea.go
|
@ -11,10 +11,12 @@ package tea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
isatty "github.com/mattn/go-isatty"
|
||||||
te "github.com/muesli/termenv"
|
te "github.com/muesli/termenv"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
@ -57,12 +59,56 @@ func Batch(cmds ...Cmd) Cmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProgramOption is used to set options when intializing a Program. Program can
|
||||||
|
// accept a variable number of options.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// p := NewProgram(model, WithInput(someInput), WithOutput(someOutput))
|
||||||
|
type ProgramOption func(*Program)
|
||||||
|
|
||||||
|
// WithOutput sets the output which, by default, is stdout. In most cases you
|
||||||
|
// won't need to use this.
|
||||||
|
func WithOutput(output *os.File) ProgramOption {
|
||||||
|
return func(m *Program) {
|
||||||
|
m.output = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInput sets the input which, by default, is stdin. In most cases you
|
||||||
|
// won't need to use this.
|
||||||
|
func WithInput(input io.Reader) ProgramOption {
|
||||||
|
return func(m *Program) {
|
||||||
|
m.input = input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInputDisables disables input. Use this with caution: if you disable
|
||||||
|
// input users will not be able to exit your program until it exits itself.
|
||||||
|
func WithInputDisabled() ProgramOption {
|
||||||
|
return func(m *Program) {
|
||||||
|
m.input = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutCatchPanics disables the panic catching that Bubble Tea does by
|
||||||
|
// default. Note that if panic catching is disabled the terminal will be in a
|
||||||
|
// fairly unusable state after a panic because Bubble Tea will not perform its
|
||||||
|
// usual cleanup on exit.
|
||||||
|
func WithoutCatchPanics() ProgramOption {
|
||||||
|
return func(m *Program) {
|
||||||
|
m.CatchPanics = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Program is a terminal user interface.
|
// Program is a terminal user interface.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
initialModel Model
|
initialModel Model
|
||||||
|
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
|
|
||||||
output *os.File // where to send output. this will usually be os.Stdout.
|
output *os.File // where to send output. this will usually be os.Stdout.
|
||||||
|
input io.Reader // this will usually be os.Stdin.
|
||||||
renderer *renderer
|
renderer *renderer
|
||||||
altScreenActive bool
|
altScreenActive bool
|
||||||
|
|
||||||
|
@ -106,12 +152,20 @@ func HideCursor() Msg {
|
||||||
type hideCursorMsg struct{}
|
type hideCursorMsg struct{}
|
||||||
|
|
||||||
// NewProgram creates a new Program.
|
// NewProgram creates a new Program.
|
||||||
func NewProgram(model Model) *Program {
|
func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||||
return &Program{
|
p := &Program{
|
||||||
initialModel: model,
|
initialModel: model,
|
||||||
output: os.Stdout,
|
output: os.Stdout,
|
||||||
|
input: os.Stdin,
|
||||||
CatchPanics: true,
|
CatchPanics: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply all options to program
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start initializes the program.
|
// Start initializes the program.
|
||||||
|
@ -136,11 +190,15 @@ func (p *Program) Start() error {
|
||||||
|
|
||||||
p.renderer = newRenderer(p.output, &p.mtx)
|
p.renderer = newRenderer(p.output, &p.mtx)
|
||||||
|
|
||||||
|
// Check if output is a TTY before entering raw mode, hiding the cursor and
|
||||||
|
// so on.
|
||||||
|
if isatty.IsTerminal(p.output.Fd()) {
|
||||||
err := initTerminal(p.output)
|
err := initTerminal(p.output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer restoreTerminal(p.output)
|
defer restoreTerminal(p.output)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize program
|
// Initialize program
|
||||||
model := p.initialModel
|
model := p.initialModel
|
||||||
|
@ -159,15 +217,21 @@ 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 {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
msg, err := readInput(os.Stdin)
|
msg, err := readInput(p.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// If we get EOF just stop listening for input
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
errs <- err
|
errs <- err
|
||||||
}
|
}
|
||||||
msgs <- msg
|
msgs <- msg
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// Get initial terminal size
|
// Get initial terminal size
|
||||||
go func() {
|
go func() {
|
||||||
|
|
Loading…
Reference in New Issue