mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
Add ProgramOptions for entering the altscreen and enabling the mouse
Because they run ansyncronously, the analogous commands, when run in a Model's Init() function, could fire after the renderer has starting writing to output, rendering artifacts to the commandline.
This commit is contained in:
@@ -16,7 +16,8 @@ type model int
|
||||
type tickMsg time.Time
|
||||
|
||||
func main() {
|
||||
if err := tea.NewProgram(model(5)).Start(); err != nil {
|
||||
p := tea.NewProgram(model(5), tea.WithAltScreen())
|
||||
if err := p.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := tea.NewProgram(model{}).Start(); err != nil {
|
||||
p := tea.NewProgram(model{}, tea.WithAltScreen(), tea.WithMouseAllMotion())
|
||||
if err := p.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -22,7 +23,7 @@ type model struct {
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return tea.Batch(tea.EnterAltScreen, tea.EnableMouseAllMotion)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
@@ -53,15 +53,15 @@ func main() {
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
p := tea.NewProgram(model{content: string(content)})
|
||||
p := tea.NewProgram(
|
||||
model{content: string(content)},
|
||||
|
||||
// Use the full size of the terminal in its "alternate screen buffer"
|
||||
p.EnterAltScreen()
|
||||
defer p.ExitAltScreen()
|
||||
// Use the full size of the terminal in its "alternate screen buffer"
|
||||
tea.WithAltScreen(),
|
||||
|
||||
// We also turn on mouse support so we can track the mouse wheel
|
||||
p.EnableMouseCellMotion()
|
||||
defer p.DisableMouseCellMotion()
|
||||
// Also turn on mouse support so we can track the mouse wheel
|
||||
tea.WithMouseCellMotion(),
|
||||
)
|
||||
|
||||
if err := p.Start(); err != nil {
|
||||
fmt.Println("could not run program:", err)
|
||||
|
||||
214
tea.go
214
tea.go
@@ -25,7 +25,7 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// Msg represents an action and is usually the result of an IO operation. It's
|
||||
// Msg represents an action and is usually the result of an IO operation. It
|
||||
// triggers the Update function, and henceforth, the UI.
|
||||
type Msg interface{}
|
||||
|
||||
@@ -52,23 +52,13 @@ type Model interface {
|
||||
// function.
|
||||
type Cmd func() Msg
|
||||
|
||||
// Batch performs a bunch of commands concurrently with no ordering guarantees
|
||||
// about the results.
|
||||
func Batch(cmds ...Cmd) Cmd {
|
||||
if len(cmds) == 0 {
|
||||
return nil
|
||||
}
|
||||
return func() Msg {
|
||||
return batchMsg(cmds)
|
||||
}
|
||||
}
|
||||
|
||||
// ProgramOption is used to set options when initializing 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
|
||||
@@ -98,6 +88,67 @@ func WithoutCatchPanics() ProgramOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAltScreen starts the program with the alternate screen buffer enabled
|
||||
// (i.e. the program starts in full window mode). Note that the altscreen will
|
||||
// be automatically exited when the program quits.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// p := tea.NewProgram(Model{}, tea.WithAltScreen())
|
||||
// if err := p.Start(); err != nil {
|
||||
// fmt.Println("Error running program:", err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
//
|
||||
// To enter the altscreen once the program has already started running use the
|
||||
// EnterAltScreen command.
|
||||
func WithAltScreen() ProgramOption {
|
||||
return func(p *Program) {
|
||||
p.startupOptions |= withAltScreen
|
||||
}
|
||||
}
|
||||
|
||||
// WithMouseCellMotion starts the program with the mouse enabled in "cell
|
||||
// motion" mode.
|
||||
//
|
||||
// Cell motion mode enables mouse click, release, and wheel events. Mouse
|
||||
// movement events are also captured if a mouse button is pressed (i.e., drag
|
||||
// events). Cell motion mode is better supported than all motion mode.
|
||||
//
|
||||
// To enable mouse cell motion once the program has already started running use
|
||||
// the EnableMouseCellMotion command. To disable the mouse when the program is
|
||||
// running use the DisableMouse command.
|
||||
//
|
||||
// The mouse will be automatically disabled when the program exits.
|
||||
func WithMouseCellMotion() ProgramOption {
|
||||
return func(p *Program) {
|
||||
p.startupOptions |= withMouseCellMotion // set
|
||||
p.startupOptions &^= withMouseAllMotion // clear
|
||||
}
|
||||
}
|
||||
|
||||
// WithMouseAllMotion starts the program with the mouse enabled in "all motion"
|
||||
// mode.
|
||||
//
|
||||
// EnableMouseAllMotion is a special command that enables mouse click, release,
|
||||
// wheel, and motion events, which are delivered regardless of whether a mouse
|
||||
// button is pressed, effectively enabling support for hover interactions.
|
||||
//
|
||||
// Many modern terminals support this, but not all. If in doubt, use
|
||||
// EnableMouseCellMotion instead.
|
||||
//
|
||||
// To enable the mouse once the program has already started running use the
|
||||
// EnableMouseAllMotion command. To disable the mouse when the program is
|
||||
// running use the DisableMouse command.
|
||||
//
|
||||
// The mouse will be automatically disabled when the program exits.
|
||||
func WithMouseAllMotion() ProgramOption {
|
||||
return func(p *Program) {
|
||||
p.startupOptions |= withMouseAllMotion // set
|
||||
p.startupOptions &^= withMouseCellMotion // clear
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutRenderer disables the renderer. When this is set output and log
|
||||
// statements will be plainly sent to stdout (or another output if one is set)
|
||||
// without any rendering and redrawing logic. In other words, printing and
|
||||
@@ -112,6 +163,19 @@ func WithoutRenderer() ProgramOption {
|
||||
}
|
||||
}
|
||||
|
||||
// startupOptions contains configuration options to be run while the program
|
||||
// is initializing.
|
||||
//
|
||||
// The options here are treated as bits.
|
||||
type startupOptions byte
|
||||
|
||||
// Available startup options.
|
||||
const (
|
||||
withAltScreen startupOptions = 1 << iota
|
||||
withMouseCellMotion
|
||||
withMouseAllMotion
|
||||
)
|
||||
|
||||
// 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.
|
||||
@@ -144,6 +208,10 @@ func (i inputStatus) String() string {
|
||||
type Program struct {
|
||||
initialModel Model
|
||||
|
||||
// Configuration options that will set as the program is initializing,
|
||||
// treated as bits. These options can be set via various ProgramOptions.
|
||||
startupOptions startupOptions
|
||||
|
||||
mtx *sync.Mutex
|
||||
|
||||
output io.Writer // where to send output. this will usually be os.Stdout.
|
||||
@@ -172,6 +240,28 @@ type Program struct {
|
||||
windowsStdin *os.File //nolint:golint,structcheck,unused
|
||||
}
|
||||
|
||||
// Batch performs a bunch of commands concurrently with no ordering guarantees
|
||||
// about the results. Use a Batch to return several commands.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func (m model) Init() Cmd {
|
||||
// return tea.Batch(someCommand, someOtherCommand)
|
||||
// }
|
||||
//
|
||||
func Batch(cmds ...Cmd) Cmd {
|
||||
if len(cmds) == 0 {
|
||||
return nil
|
||||
}
|
||||
return func() Msg {
|
||||
return batchMsg(cmds)
|
||||
}
|
||||
}
|
||||
|
||||
// batchMsg is the internal message used to perform a bunch of commands. You
|
||||
// can send a batchMsg with Batch.
|
||||
type batchMsg []Cmd
|
||||
|
||||
// Quit is a special command that tells the Bubble Tea program to exit.
|
||||
func Quit() Msg {
|
||||
return quitMsg{}
|
||||
@@ -183,6 +273,10 @@ type quitMsg struct{}
|
||||
|
||||
// EnterAltScreen is a special command that tells the Bubble Tea program to
|
||||
// enter alternate screen buffer.
|
||||
//
|
||||
// Because commands run asynchronously, this command should not be used in your
|
||||
// model's Init function. To initialize your program with the altscreen enabled
|
||||
// use the WithAltScreen ProgramOption instead.
|
||||
func EnterAltScreen() Msg {
|
||||
return enterAltScreenMsg{}
|
||||
}
|
||||
@@ -193,21 +287,45 @@ func EnterAltScreen() Msg {
|
||||
type enterAltScreenMsg struct{}
|
||||
|
||||
// ExitAltScreen is a special command that tells the Bubble Tea program to exit
|
||||
// alternate screen buffer.
|
||||
// the alternate screen buffer. This command should be used to exit the
|
||||
// alternate screen buffer while the program is running.
|
||||
//
|
||||
// Note that the alternate screen buffer will be automatically exited when the
|
||||
// program quits.
|
||||
func ExitAltScreen() Msg {
|
||||
return exitAltScreenMsg{}
|
||||
}
|
||||
|
||||
// exitAltScreenMsg in an internal message signals that the program should exit
|
||||
// alternate screen buffer. You can send a exitAltScreenMsg with ExitAltScreen.
|
||||
type exitAltScreenMsg struct{}
|
||||
|
||||
// EnableMouseCellMotion is a special command that enables mouse click,
|
||||
// release, and wheel events. Mouse movement events are also captured if
|
||||
// a mouse button is pressed (i.e., drag events).
|
||||
//
|
||||
// Because commands run asynchronously, this command should not be used in your
|
||||
// model's Init function. Use the WithMouseCellMotion ProgramOption instead.
|
||||
func EnableMouseCellMotion() Msg {
|
||||
return enableMouseCellMotionMsg{}
|
||||
}
|
||||
|
||||
// enableMouseCellMotionMsg is a special command that signals to start
|
||||
// listening for "cell motion" type mouse events (ESC[?1002l). To send an
|
||||
// enableMouseCellMotionMsg, use the EnableMouseCellMotion command.
|
||||
type enableMouseCellMotionMsg struct{}
|
||||
|
||||
// EnableMouseCellMotion is a special command that enables mouse click,
|
||||
// release, wheel, events. Mouse movement events are also captured if a mouse
|
||||
// button is pressed (i.e., drag events).
|
||||
func EnableMouseCellMotion() Msg {
|
||||
return enableMouseCellMotionMsg{}
|
||||
// EnableMouseAllMotion is a special command that enables mouse click, release,
|
||||
// wheel, and motion events, which are delivered regardless of whether a mouse
|
||||
// button is pressed, effectively enabling support for hover interactions.
|
||||
//
|
||||
// Many modern terminals support this, but not all. If in doubt, use
|
||||
// EnableMouseCellMotion instead.
|
||||
//
|
||||
// Because commands run asynchronously, this command should not be used in your
|
||||
// model's Init function. Use the WithMouseAllMotion ProgramOption instead.
|
||||
func EnableMouseAllMotion() Msg {
|
||||
return enableMouseAllMotionMsg{}
|
||||
}
|
||||
|
||||
// enableMouseAllMotionMsg is a special command that signals to start listening
|
||||
@@ -215,33 +333,19 @@ func EnableMouseCellMotion() Msg {
|
||||
// enableMouseAllMotionMsg, use the EnableMouseAllMotion command.
|
||||
type enableMouseAllMotionMsg struct{}
|
||||
|
||||
// EnableMouseAllMotion is a special command that enables mouse click, release,
|
||||
// and wheel events. Mouse movement events are delivered regardless of whether
|
||||
// a button is pressed. Many modern terminals support this, but not all. If in
|
||||
// doubt, use EnableMouseCellMotion instead.
|
||||
func EnableMouseAllMotion() Msg {
|
||||
return enableMouseAllMotionMsg{}
|
||||
// DisableMouse is a special command that stops listening for mouse events.
|
||||
func DisableMouse() Msg {
|
||||
return disableMouseMsg{}
|
||||
}
|
||||
|
||||
// disableMouseMsg is an internal message that that signals to stop listening
|
||||
// for mouse events. To send a disableMouseMsg, use the DisableMouse command.
|
||||
type disableMouseMsg struct{}
|
||||
|
||||
// DisableMouse is a special command that stops listening for mouse events.
|
||||
func DisableMouse() Msg {
|
||||
return disableMouseMsg{}
|
||||
}
|
||||
|
||||
// exitAltScreenMsg in an internal message signals that the program should exit
|
||||
// alternate screen buffer. You can send a exitAltScreenMsg with ExitAltScreen.
|
||||
type exitAltScreenMsg struct{}
|
||||
|
||||
// batchMsg is the internal message used to perform a bunch of commands. You
|
||||
// can send a batchMsg with Batch.
|
||||
type batchMsg []Cmd
|
||||
|
||||
// WindowSizeMsg is used to report on the terminal size. It's sent to Update
|
||||
// once initially and then on every terminal resize.
|
||||
// WindowSizeMsg is used to report the terminal size. It's sent to Update once
|
||||
// initially and then on every terminal resize. Note that Windows does not
|
||||
// have support for reporting when resizes occur as it does not support the
|
||||
// SIGWINCH signal.
|
||||
type WindowSizeMsg struct {
|
||||
Width int
|
||||
Height int
|
||||
@@ -347,11 +451,6 @@ func (p *Program) Start() error {
|
||||
}()
|
||||
}
|
||||
|
||||
// If no renderer is set use the standard one.
|
||||
if p.renderer == nil {
|
||||
p.renderer = newRenderer(p.output, p.mtx)
|
||||
}
|
||||
|
||||
// Check if output is a TTY before entering raw mode, hiding the cursor and
|
||||
// so on.
|
||||
{
|
||||
@@ -364,6 +463,21 @@ func (p *Program) Start() error {
|
||||
}()
|
||||
}
|
||||
|
||||
// If no renderer is set use the standard one.
|
||||
if p.renderer == nil {
|
||||
p.renderer = newRenderer(p.output, p.mtx)
|
||||
}
|
||||
|
||||
// Honor program startup options.
|
||||
if p.startupOptions&withAltScreen != 0 {
|
||||
p.EnterAltScreen()
|
||||
}
|
||||
if p.startupOptions&withMouseCellMotion != 0 {
|
||||
p.EnableMouseCellMotion()
|
||||
} else if p.startupOptions&withMouseAllMotion != 0 {
|
||||
p.EnableMouseAllMotion()
|
||||
}
|
||||
|
||||
// Initialize program
|
||||
model := p.initialModel
|
||||
initCmd := model.Init()
|
||||
@@ -490,7 +604,7 @@ func (p *Program) Start() error {
|
||||
// EnterAltScreen enters the alternate screen buffer, which consumes the entire
|
||||
// terminal window. ExitAltScreen will return the terminal to its former state.
|
||||
//
|
||||
// Deprecated. Use the EnterAltScreen() command instead.
|
||||
// Deprecated. Use the WithAltScreen ProgramOption instead.
|
||||
func (p *Program) EnterAltScreen() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
@@ -510,7 +624,7 @@ func (p *Program) EnterAltScreen() {
|
||||
|
||||
// ExitAltScreen exits the alternate screen buffer.
|
||||
//
|
||||
// Deprecated. Use the ExitAltScreen() command instead.
|
||||
// Deprecated. The altscreen will exited automatically when the program exits.
|
||||
func (p *Program) ExitAltScreen() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
@@ -530,7 +644,7 @@ func (p *Program) ExitAltScreen() {
|
||||
// EnableMouseCellMotion enables mouse click, release, wheel and motion events
|
||||
// if a mouse button is pressed (i.e., drag events).
|
||||
//
|
||||
// Deprecated. Use the EnableMouseCellMotion() command instead.
|
||||
// Deprecated. Use the WithMouseCellMotion ProgramOption instead.
|
||||
func (p *Program) EnableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
@@ -540,7 +654,7 @@ func (p *Program) EnableMouseCellMotion() {
|
||||
// DisableMouseCellMotion disables Mouse Cell Motion tracking. This will be
|
||||
// called automatically when exiting a Bubble Tea program.
|
||||
//
|
||||
// Deprecated. Use the DisableMouse() command instead.
|
||||
// Deprecated. The mouse will automatically be disabled when the program exits.
|
||||
func (p *Program) DisableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
@@ -551,7 +665,7 @@ func (p *Program) DisableMouseCellMotion() {
|
||||
// regardless of whether a mouse button is pressed. Many modern terminals
|
||||
// support this, but not all.
|
||||
//
|
||||
// Deprecated. Use the EnableMouseAllMotion() command instead.
|
||||
// Deprecated. Use the WithMouseAllMotion ProgramOption instead.
|
||||
func (p *Program) EnableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
@@ -561,7 +675,7 @@ func (p *Program) EnableMouseAllMotion() {
|
||||
// DisableMouseAllMotion disables All Motion mouse tracking. This will be
|
||||
// called automatically when exiting a Bubble Tea program.
|
||||
//
|
||||
// Deprecated. Use the DisableMouse() command instead.
|
||||
// Deprecated. The mouse will automatically be disabled when the program exits.
|
||||
func (p *Program) DisableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user