forked from Mirrors/bubbletea
Read input regardless of whether or not it's a TTY
This commit also contains some refactors: * Refactor away inputStatus type * Refactor away program.inputIsTTY member * Simplify how we setup and restore input when it's a TTY
This commit is contained in:
parent
e110b5ca1b
commit
49a5d16579
|
@ -24,7 +24,7 @@ func WithOutput(output io.Writer) ProgramOption {
|
||||||
func WithInput(input io.Reader) ProgramOption {
|
func WithInput(input io.Reader) ProgramOption {
|
||||||
return func(m *Program) {
|
return func(m *Program) {
|
||||||
m.input = input
|
m.input = input
|
||||||
m.inputStatus = customInput
|
m.startupOptions |= withCustomInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
59
tea.go
59
tea.go
|
@ -52,8 +52,8 @@ type Model interface {
|
||||||
// function.
|
// function.
|
||||||
type Cmd func() Msg
|
type Cmd func() Msg
|
||||||
|
|
||||||
// startupOptions contains configuration options to be run while the program
|
// Options to customize the program during its initialization. These are
|
||||||
// is initializing.
|
// generally set with ProgramOptions.
|
||||||
//
|
//
|
||||||
// The options here are treated as bits.
|
// The options here are treated as bits.
|
||||||
type startupOptions byte
|
type startupOptions byte
|
||||||
|
@ -62,42 +62,14 @@ func (s startupOptions) has(option startupOptions) bool {
|
||||||
return s&option != 0
|
return s&option != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Available startup options.
|
|
||||||
const (
|
const (
|
||||||
withAltScreen startupOptions = 1 << iota
|
withAltScreen startupOptions = 1 << iota
|
||||||
withMouseCellMotion
|
withMouseCellMotion
|
||||||
withMouseAllMotion
|
withMouseAllMotion
|
||||||
withInputTTY
|
withInputTTY
|
||||||
|
withCustomInput
|
||||||
)
|
)
|
||||||
|
|
||||||
// inputStatus indicates the current state of the input. By default, input is
|
|
||||||
// stdin, however we'll change this if input's not a TTY. The user can also set
|
|
||||||
// the input.
|
|
||||||
type inputStatus int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Generally this will be stdin.
|
|
||||||
//
|
|
||||||
// Lint ignore note: this is the implicit default value. While it's not
|
|
||||||
// checked explicitly, it's presence nullifies the other possible values
|
|
||||||
// of this type in logical statements.
|
|
||||||
defaultInput inputStatus = iota // nolint:golint,deadcode,unused,varcheck
|
|
||||||
|
|
||||||
// The user explicitly set the input.
|
|
||||||
customInput
|
|
||||||
|
|
||||||
// We've opened a TTY for input.
|
|
||||||
managedInput
|
|
||||||
)
|
|
||||||
|
|
||||||
func (i inputStatus) String() string {
|
|
||||||
return [...]string{
|
|
||||||
"default input",
|
|
||||||
"custom input",
|
|
||||||
"managed input",
|
|
||||||
}[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Program is a terminal user interface.
|
// Program is a terminal user interface.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
initialModel Model
|
initialModel Model
|
||||||
|
@ -122,8 +94,6 @@ type Program struct {
|
||||||
// is on by default.
|
// is on by default.
|
||||||
CatchPanics bool
|
CatchPanics bool
|
||||||
|
|
||||||
inputStatus inputStatus
|
|
||||||
inputIsTTY bool
|
|
||||||
outputIsTTY bool
|
outputIsTTY bool
|
||||||
console console.Console
|
console console.Console
|
||||||
|
|
||||||
|
@ -311,22 +281,19 @@ func (p *Program) Start() error {
|
||||||
p.input = f
|
p.input = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is input a terminal?
|
|
||||||
if f, ok := p.input.(*os.File); ok {
|
|
||||||
p.inputIsTTY = isatty.IsTerminal(f.Fd())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If input is not a terminal, and the user hasn't set a custom input, open
|
// If input is not a terminal, and the user hasn't set a custom input, open
|
||||||
// a TTY so we can capture input as normal. This will allow things to "just
|
// a TTY so we can capture input as normal. This will allow things to "just
|
||||||
// work" in cases where data was piped or redirected into this application.
|
// work" in cases where data was piped or redirected into this application.
|
||||||
if !p.inputIsTTY && p.inputStatus != customInput {
|
if f, ok := p.input.(*os.File); ok {
|
||||||
f, err := openInputTTY()
|
inputIsTTY := isatty.IsTerminal(f.Fd())
|
||||||
if err != nil {
|
|
||||||
return err
|
if !inputIsTTY && !p.startupOptions.has(withCustomInput) {
|
||||||
|
f, err := openInputTTY()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.input = f
|
||||||
}
|
}
|
||||||
p.input = f
|
|
||||||
p.inputIsTTY = true
|
|
||||||
p.inputStatus = managedInput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for SIGINT. Note that in most cases ^C will not send an
|
// Listen for SIGINT. Note that in most cases ^C will not send an
|
||||||
|
@ -394,7 +361,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.inputIsTTY {
|
if p.input != nil {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
msg, err := readInput(p.input)
|
msg, err := readInput(p.input)
|
||||||
|
|
14
tty.go
14
tty.go
|
@ -12,10 +12,7 @@ func (p *Program) initTerminal() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.inputIsTTY {
|
if p.console != nil {
|
||||||
if p.console == nil {
|
|
||||||
return errors.New("no console")
|
|
||||||
}
|
|
||||||
err = p.console.SetRaw()
|
err = p.console.SetRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -35,11 +32,6 @@ func (p Program) restoreTerminal() error {
|
||||||
showCursor(p.output)
|
showCursor(p.output)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.restoreInput(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Console will only be set if input is a TTY.
|
|
||||||
if p.console != nil {
|
if p.console != nil {
|
||||||
err := p.console.Reset()
|
err := p.console.Reset()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -47,5 +39,9 @@ func (p Program) restoreTerminal() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := p.restoreInput(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
39
tty_unix.go
39
tty_unix.go
|
@ -3,7 +3,6 @@
|
||||||
package tea
|
package tea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -11,39 +10,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Program) initInput() error {
|
func (p *Program) initInput() error {
|
||||||
if !p.inputIsTTY {
|
// If input's a file, use console to manage it
|
||||||
return nil
|
if f, ok := p.input.(*os.File); ok {
|
||||||
|
c, err := console.ConsoleFromFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.console = c
|
||||||
}
|
}
|
||||||
|
|
||||||
// If input's a TTY this should always succeed.
|
|
||||||
f, ok := p.input.(*os.File)
|
|
||||||
if !ok {
|
|
||||||
return errInputIsNotAFile
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := console.ConsoleFromFile(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.console = c
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// On unix systems, RestoreInput closes any TTYs we opened for input. Note that
|
// On unix systems, RestoreInput closes any TTYs we opened for input. Note that
|
||||||
// we don't do this on Windows as it causes the prompt to not be drawn until the
|
// we don't do this on Windows as it causes the prompt to not be drawn until
|
||||||
// terminal receives a keypress rather than appearing promptly after the program
|
// the terminal receives a keypress rather than appearing promptly after the
|
||||||
// exits.
|
// program exits.
|
||||||
func (p *Program) restoreInput() error {
|
func (p *Program) restoreInput() error {
|
||||||
if p.inputStatus == managedInput {
|
if p.console != nil {
|
||||||
f, ok := p.input.(*os.File)
|
return p.console.Close()
|
||||||
if !ok {
|
|
||||||
return errors.New("could not close input")
|
|
||||||
}
|
|
||||||
err := f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ func (p *Program) restoreInput() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open the Windows equivalent of a TTY.
|
||||||
func openInputTTY() (*os.File, error) {
|
func openInputTTY() (*os.File, error) {
|
||||||
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0644)
|
f, err := os.OpenFile("CONIN$", os.O_RDWR, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue