forked from Mirrors/bubbletea
chore: make input options mutually exclusive
This commit is contained in:
parent
25022e9789
commit
fcc805f3da
|
@ -37,18 +37,20 @@ func WithOutput(output io.Writer) ProgramOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithInput sets the input which, by default, is stdin. In most cases you
|
// WithInput sets the input which, by default, is stdin. In most cases you
|
||||||
// won't need to use this.
|
// won't need to use this. To disable input entirely pass nil.
|
||||||
|
//
|
||||||
|
// p := NewProgram(model, WithInput(nil))
|
||||||
func WithInput(input io.Reader) ProgramOption {
|
func WithInput(input io.Reader) ProgramOption {
|
||||||
return func(p *Program) {
|
return func(p *Program) {
|
||||||
p.input = input
|
p.input = input
|
||||||
p.startupOptions |= withCustomInput
|
p.inputType = customInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithInputTTY opens a new TTY for input (or console input device on Windows).
|
// WithInputTTY opens a new TTY for input (or console input device on Windows).
|
||||||
func WithInputTTY() ProgramOption {
|
func WithInputTTY() ProgramOption {
|
||||||
return func(p *Program) {
|
return func(p *Program) {
|
||||||
p.startupOptions |= withInputTTY
|
p.inputType = ttyInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,13 @@ func TestOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("input", func(t *testing.T) {
|
t.Run("custom input", func(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
p := NewProgram(nil, WithInput(&b))
|
p := NewProgram(nil, WithInput(&b))
|
||||||
if p.input != &b {
|
if p.input != &b {
|
||||||
t.Errorf("expected input to custom, got %v", p.input)
|
t.Errorf("expected input to custom, got %v", p.input)
|
||||||
}
|
}
|
||||||
if p.startupOptions&withCustomInput == 0 {
|
if p.inputType != customInput {
|
||||||
t.Errorf("expected startup options to have custom input set, got %v", p.input)
|
t.Errorf("expected startup options to have custom input set, got %v", p.input)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -49,6 +49,25 @@ func TestOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("input options", func(t *testing.T) {
|
||||||
|
exercise := func(t *testing.T, opt ProgramOption, expect inputType) {
|
||||||
|
p := NewProgram(nil, opt)
|
||||||
|
if p.inputType != expect {
|
||||||
|
t.Errorf("expected input type %s, got %s", expect, p.inputType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("tty input", func(t *testing.T) {
|
||||||
|
exercise(t, WithInputTTY(), ttyInput)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom input", func(t *testing.T) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
exercise(t, WithInput(&b), customInput)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("startup options", func(t *testing.T) {
|
t.Run("startup options", func(t *testing.T) {
|
||||||
exercise := func(t *testing.T, opt ProgramOption, expect startupOptions) {
|
exercise := func(t *testing.T, opt ProgramOption, expect startupOptions) {
|
||||||
p := NewProgram(nil, opt)
|
p := NewProgram(nil, opt)
|
||||||
|
@ -57,10 +76,6 @@ func TestOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("input tty", func(t *testing.T) {
|
|
||||||
exercise(t, WithInputTTY(), withInputTTY)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("alt screen", func(t *testing.T) {
|
t.Run("alt screen", func(t *testing.T) {
|
||||||
exercise(t, WithAltScreen(), withAltScreen)
|
exercise(t, WithAltScreen(), withAltScreen)
|
||||||
})
|
})
|
||||||
|
@ -100,10 +115,13 @@ func TestOptions(t *testing.T) {
|
||||||
|
|
||||||
t.Run("multiple", func(t *testing.T) {
|
t.Run("multiple", func(t *testing.T) {
|
||||||
p := NewProgram(nil, WithMouseAllMotion(), WithAltScreen(), WithInputTTY())
|
p := NewProgram(nil, WithMouseAllMotion(), WithAltScreen(), WithInputTTY())
|
||||||
for _, opt := range []startupOptions{withMouseAllMotion, withAltScreen, withInputTTY} {
|
for _, opt := range []startupOptions{withMouseAllMotion, withAltScreen} {
|
||||||
if !p.startupOptions.has(opt) {
|
if !p.startupOptions.has(opt) {
|
||||||
t.Errorf("expected startup options have %v, got %v", opt, p.startupOptions)
|
t.Errorf("expected startup options have %v, got %v", opt, p.startupOptions)
|
||||||
}
|
}
|
||||||
|
if p.inputType != ttyInput {
|
||||||
|
t.Errorf("expected input to be %v, got %v", opt, p.startupOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
32
tea.go
32
tea.go
|
@ -60,6 +60,24 @@ type Cmd func() Msg
|
||||||
|
|
||||||
type handlers []chan struct{}
|
type handlers []chan struct{}
|
||||||
|
|
||||||
|
type inputType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultInput inputType = iota
|
||||||
|
ttyInput
|
||||||
|
customInput
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements the stringer interface for [inputType]. It is inteded to
|
||||||
|
// be used in testing.
|
||||||
|
func (i inputType) String() string {
|
||||||
|
return [...]string{
|
||||||
|
"default input",
|
||||||
|
"tty input",
|
||||||
|
"custom input",
|
||||||
|
}[i]
|
||||||
|
}
|
||||||
|
|
||||||
// Options to customize the program during its initialization. These are
|
// Options to customize the program during its initialization. These are
|
||||||
// generally set with ProgramOptions.
|
// generally set with ProgramOptions.
|
||||||
//
|
//
|
||||||
|
@ -74,8 +92,6 @@ const (
|
||||||
withAltScreen startupOptions = 1 << iota
|
withAltScreen startupOptions = 1 << iota
|
||||||
withMouseCellMotion
|
withMouseCellMotion
|
||||||
withMouseAllMotion
|
withMouseAllMotion
|
||||||
withInputTTY
|
|
||||||
withCustomInput
|
|
||||||
withANSICompressor
|
withANSICompressor
|
||||||
withoutSignalHandler
|
withoutSignalHandler
|
||||||
|
|
||||||
|
@ -94,6 +110,8 @@ type Program struct {
|
||||||
// treated as bits. These options can be set via various ProgramOptions.
|
// treated as bits. These options can be set via various ProgramOptions.
|
||||||
startupOptions startupOptions
|
startupOptions startupOptions
|
||||||
|
|
||||||
|
inputType inputType
|
||||||
|
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
@ -141,7 +159,6 @@ type QuitMsg struct{}
|
||||||
func NewProgram(model Model, opts ...ProgramOption) *Program {
|
func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||||
p := &Program{
|
p := &Program{
|
||||||
initialModel: model,
|
initialModel: model,
|
||||||
input: os.Stdin,
|
|
||||||
msgs: make(chan Msg),
|
msgs: make(chan Msg),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,8 +388,11 @@ func (p *Program) Run() (Model, error) {
|
||||||
|
|
||||||
defer p.cancel()
|
defer p.cancel()
|
||||||
|
|
||||||
switch {
|
switch p.inputType {
|
||||||
case p.startupOptions.has(withInputTTY):
|
case defaultInput:
|
||||||
|
p.input = os.Stdin
|
||||||
|
|
||||||
|
case ttyInput:
|
||||||
// Open a new TTY, by request
|
// Open a new TTY, by request
|
||||||
f, err := openInputTTY()
|
f, err := openInputTTY()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -381,7 +401,7 @@ func (p *Program) Run() (Model, error) {
|
||||||
defer f.Close() //nolint:errcheck
|
defer f.Close() //nolint:errcheck
|
||||||
p.input = f
|
p.input = f
|
||||||
|
|
||||||
case !p.startupOptions.has(withCustomInput):
|
case customInput:
|
||||||
// If the user hasn't set a custom input, and input's not a terminal,
|
// If the user hasn't set a custom input, and input's not a terminal,
|
||||||
// open a TTY so we can capture input as normal. This will allow things
|
// open 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
|
// to "just work" in cases where data was piped or redirected into this
|
||||||
|
|
Loading…
Reference in New Issue