mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
Tea core now sends the terminal dimensions on start and when the window is resized (except on Windows where SIGWINCH, the resize signal, is not supported).
193 lines
3.8 KiB
Go
193 lines
3.8 KiB
Go
package tea
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/muesli/termenv"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
)
|
|
|
|
// Msg represents an action and is usually the result of an IO operation. It's
|
|
// triggers the Update function, and henceforth, the UI.
|
|
type Msg interface{}
|
|
|
|
// Model contains the program's state.
|
|
type Model interface{}
|
|
|
|
// Cmd is an IO operation. If it's nil it's considered a no-op.
|
|
type Cmd func() Msg
|
|
|
|
// Batch peforms 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)
|
|
}
|
|
}
|
|
|
|
// Init is the first function that will be called. It returns your initial
|
|
// model and runs an optional command.
|
|
type Init func() (Model, Cmd)
|
|
|
|
// Update is called when a message is received. It may update the model and/or
|
|
// send a command.
|
|
type Update func(Msg, Model) (Model, Cmd)
|
|
|
|
// View produces a string which will be rendered to the terminal.
|
|
type View func(Model) string
|
|
|
|
// Program is a terminal user interface.
|
|
type Program struct {
|
|
init Init
|
|
update Update
|
|
view View
|
|
}
|
|
|
|
// Quit is a special command that tells the program to exit.
|
|
func Quit() Msg {
|
|
return quitMsg{}
|
|
}
|
|
|
|
// quitMsg in an internal message signals that the program should quit. You can
|
|
// send a quitMsg with Quit.
|
|
type quitMsg 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 once
|
|
// initially and then on every terminal resize.
|
|
type WindowSizeMsg struct {
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
// NewProgram creates a new Program.
|
|
func NewProgram(init Init, update Update, view View) *Program {
|
|
return &Program{
|
|
init: init,
|
|
update: update,
|
|
view: view,
|
|
}
|
|
}
|
|
|
|
// Start initializes the program.
|
|
func (p *Program) Start() error {
|
|
var (
|
|
model Model
|
|
cmd Cmd
|
|
cmds = make(chan Cmd)
|
|
msgs = make(chan Msg)
|
|
errs = make(chan error)
|
|
done = make(chan struct{})
|
|
|
|
output *os.File = os.Stdout
|
|
mrRenderer = newRenderer(output)
|
|
)
|
|
|
|
err := initTerminal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer restoreTerminal()
|
|
|
|
// Initialize program
|
|
model, cmd = p.init()
|
|
if cmd != nil {
|
|
go func() {
|
|
cmds <- cmd
|
|
}()
|
|
}
|
|
|
|
// Start renderer
|
|
mrRenderer.start()
|
|
|
|
// Render initial view
|
|
mrRenderer.write(p.view(model))
|
|
|
|
// Subscribe to user input
|
|
go func() {
|
|
for {
|
|
msg, err := ReadKey(os.Stdin)
|
|
if err != nil {
|
|
errs <- err
|
|
}
|
|
msgs <- KeyMsg(msg)
|
|
}
|
|
}()
|
|
|
|
// Get initial terminal size
|
|
go func() {
|
|
w, h, err := terminal.GetSize(int(output.Fd()))
|
|
if err != nil {
|
|
errs <- err
|
|
}
|
|
msgs <- WindowSizeMsg{w, h}
|
|
}()
|
|
|
|
// Listen for window resizes
|
|
go listenForResize(output, msgs, errs)
|
|
|
|
// Process commands
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case cmd := <-cmds:
|
|
if cmd != nil {
|
|
go func() {
|
|
msgs <- cmd()
|
|
}()
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Handle updates and draw
|
|
for {
|
|
select {
|
|
case err := <-errs:
|
|
close(done)
|
|
return err
|
|
case msg := <-msgs:
|
|
|
|
// Handle quit message
|
|
if _, ok := msg.(quitMsg); ok {
|
|
mrRenderer.stop()
|
|
close(done)
|
|
return nil
|
|
}
|
|
|
|
// Process batch commands
|
|
if batchedCmds, ok := msg.(batchMsg); ok {
|
|
for _, cmd := range batchedCmds {
|
|
cmds <- cmd
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Process internal messages for the renderer
|
|
mrRenderer.handleMessages(msg)
|
|
|
|
model, cmd = p.update(msg, model) // run update
|
|
cmds <- cmd // process command (if any)
|
|
mrRenderer.write(p.view(model)) // send view to renderer
|
|
}
|
|
}
|
|
}
|
|
|
|
// AltScreen enters the alternate screen buffer.
|
|
func AltScreen() {
|
|
termenv.AltScreen()
|
|
}
|
|
|
|
// ExitAltScreen exits the alternate screen buffer.
|
|
func ExitAltScreen() {
|
|
termenv.ExitAltScreen()
|
|
}
|