mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
Msgs can now be sent to Programs with Program.Send(Msg)
This commit is contained in:
141
examples/send-msg/main.go
Normal file
141
examples/send-msg/main.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
// A simple example that shows how to send messages to a Bubble Tea program
|
||||
// from outside the program using Program.Send(Msg).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var (
|
||||
spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
||||
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Margin(1, 0)
|
||||
dotStyle = helpStyle.Copy().UnsetMargins()
|
||||
foodStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("36"))
|
||||
durationStyle = dotStyle.Copy()
|
||||
appStyle = lipgloss.NewStyle().Margin(1, 2, 0, 2)
|
||||
)
|
||||
|
||||
type resultMsg struct {
|
||||
duration time.Duration
|
||||
food string
|
||||
}
|
||||
|
||||
func (r resultMsg) String() string {
|
||||
if r.duration == 0 {
|
||||
return dotStyle.Render(strings.Repeat(".", 30))
|
||||
}
|
||||
return fmt.Sprintf("🍔 Ate %s %s", r.food,
|
||||
durationStyle.Render(r.duration.String()))
|
||||
}
|
||||
|
||||
type model struct {
|
||||
spinner spinner.Model
|
||||
results []resultMsg
|
||||
quitting bool
|
||||
}
|
||||
|
||||
func newModel() model {
|
||||
const numLastResults = 5
|
||||
s := spinner.NewModel()
|
||||
s.Style = spinnerStyle
|
||||
return model{
|
||||
spinner: s,
|
||||
results: make([]resultMsg, numLastResults),
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return spinner.Tick
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
case resultMsg:
|
||||
m.results = append(m.results[1:], msg)
|
||||
return m, nil
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
return m, cmd
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
var s string
|
||||
|
||||
if m.quitting {
|
||||
s += "That’s all for today!"
|
||||
} else {
|
||||
s += m.spinner.View() + " Eating food..."
|
||||
}
|
||||
|
||||
s += "\n\n"
|
||||
|
||||
for _, res := range m.results {
|
||||
s += res.String() + "\n"
|
||||
}
|
||||
|
||||
if !m.quitting {
|
||||
s += helpStyle.Render("Press any key to exit")
|
||||
}
|
||||
|
||||
if m.quitting {
|
||||
s += "\n"
|
||||
}
|
||||
|
||||
return appStyle.Render(s)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
p := tea.NewProgram(newModel())
|
||||
go func() {
|
||||
if err := p.Start(); err != nil {
|
||||
fmt.Println("Error running program:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Simulate activity
|
||||
go func() {
|
||||
for {
|
||||
pause := time.Duration(rand.Int63n(899)+100) * time.Millisecond
|
||||
time.Sleep(pause)
|
||||
|
||||
// Send the Bubble Tea program a message from outside the program.
|
||||
p.Send(resultMsg{food: randomFood(), duration: pause})
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func randomEmoji() string {
|
||||
emojis := []rune("🍦🧋🍡🤠👾😭🦊🐯🦆🥨🎏🍔🍒🍥🎮📦🦁🐶🐸🍕🥐🧲🚒🥇🏆🌽")
|
||||
return string(emojis[rand.Intn(len(emojis))])
|
||||
}
|
||||
|
||||
func randomFood() string {
|
||||
food := []string{"an apple", "a pear", "a gherkin", "a party gherkin",
|
||||
"a kohlrabi", "some spaghetti", "tacos", "a currywurst", "some curry",
|
||||
"a sandwich", "some peanut butter", "some cashews", "some ramen"}
|
||||
return string(food[rand.Intn(len(food))])
|
||||
}
|
||||
36
tea.go
36
tea.go
@@ -215,6 +215,8 @@ type Program struct {
|
||||
mtx *sync.Mutex
|
||||
done chan struct{}
|
||||
|
||||
msgs chan Msg
|
||||
|
||||
output io.Writer // where to send output. this will usually be os.Stdout.
|
||||
input io.Reader // this will usually be os.Stdin.
|
||||
renderer renderer
|
||||
@@ -371,7 +373,6 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||
initialModel: model,
|
||||
output: os.Stdout,
|
||||
input: os.Stdin,
|
||||
done: make(chan struct{}),
|
||||
CatchPanics: true,
|
||||
}
|
||||
|
||||
@@ -385,9 +386,11 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||
|
||||
// Start initializes the program.
|
||||
func (p *Program) Start() error {
|
||||
p.msgs = make(chan Msg)
|
||||
p.done = make(chan struct{})
|
||||
|
||||
var (
|
||||
cmds = make(chan Cmd)
|
||||
msgs = make(chan Msg)
|
||||
errs = make(chan error)
|
||||
|
||||
// If output is a file (e.g. os.Stdout) then this will be set
|
||||
@@ -435,7 +438,7 @@ func (p *Program) Start() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-sig:
|
||||
msgs <- quitMsg{}
|
||||
p.msgs <- quitMsg{}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -502,7 +505,7 @@ func (p *Program) Start() error {
|
||||
}
|
||||
errs <- err
|
||||
}
|
||||
msgs <- msg
|
||||
p.msgs <- msg
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -514,11 +517,11 @@ func (p *Program) Start() error {
|
||||
if err != nil {
|
||||
errs <- err
|
||||
}
|
||||
msgs <- WindowSizeMsg{w, h}
|
||||
p.msgs <- WindowSizeMsg{w, h}
|
||||
}()
|
||||
|
||||
// Listen for window resizes
|
||||
go listenForResize(outputAsFile, msgs, errs)
|
||||
go listenForResize(outputAsFile, p.msgs, errs)
|
||||
}
|
||||
|
||||
// Process commands
|
||||
@@ -530,7 +533,7 @@ func (p *Program) Start() error {
|
||||
case cmd := <-cmds:
|
||||
if cmd != nil {
|
||||
go func() {
|
||||
msgs <- cmd()
|
||||
p.msgs <- cmd()
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -543,7 +546,7 @@ func (p *Program) Start() error {
|
||||
case err := <-errs:
|
||||
p.shutdown(false)
|
||||
return err
|
||||
case msg := <-msgs:
|
||||
case msg := <-p.msgs:
|
||||
|
||||
// Handle special internal messages
|
||||
switch msg := msg.(type) {
|
||||
@@ -593,6 +596,21 @@ func (p *Program) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends a message to the main update function, effectively allowing
|
||||
// messages to be injected from outside the program for interoperability
|
||||
// purposes.
|
||||
//
|
||||
// If the program is not running this this will be a no-op, so it's safe to
|
||||
// send messages if the program is unstarted, or has exited.
|
||||
//
|
||||
// This method is currently provisional. The method signature may alter
|
||||
// slightly, or it may be removed in a future version of this package.
|
||||
func (p *Program) Send(msg Msg) {
|
||||
if p.msgs != nil {
|
||||
p.msgs <- msg
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown performs operations to free up resources and restore the terminal
|
||||
// to its original state.
|
||||
func (p *Program) shutdown(kill bool) {
|
||||
@@ -602,6 +620,8 @@ func (p *Program) shutdown(kill bool) {
|
||||
p.renderer.stop()
|
||||
}
|
||||
close(p.done)
|
||||
close(p.msgs)
|
||||
p.msgs = nil
|
||||
p.ExitAltScreen()
|
||||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
|
||||
Reference in New Issue
Block a user