1package tea
2
3import (
4 "time"
5)
6
7// Batch performs a bunch of commands concurrently with no ordering guarantees
8// about the results. Use a Batch to return several commands.
9//
10// Example:
11//
12// func (m model) Init() (Model, Cmd) {
13// return m, tea.Batch(someCommand, someOtherCommand)
14// }
15func Batch(cmds ...Cmd) Cmd {
16 var validCmds []Cmd //nolint:prealloc
17 for _, c := range cmds {
18 if c == nil {
19 continue
20 }
21 validCmds = append(validCmds, c)
22 }
23 switch len(validCmds) {
24 case 0:
25 return nil
26 case 1:
27 return validCmds[0]
28 default:
29 return func() Msg {
30 return BatchMsg(validCmds)
31 }
32 }
33}
34
35// BatchMsg is a message used to perform a bunch of commands concurrently with
36// no ordering guarantees. You can send a BatchMsg with Batch.
37type BatchMsg []Cmd
38
39// Sequence runs the given commands one at a time, in order. Contrast this with
40// Batch, which runs commands concurrently.
41func Sequence(cmds ...Cmd) Cmd {
42 return func() Msg {
43 return sequenceMsg(cmds)
44 }
45}
46
47// sequenceMsg is used internally to run the given commands in order.
48type sequenceMsg []Cmd
49
50// Every is a command that ticks in sync with the system clock. So, if you
51// wanted to tick with the system clock every second, minute or hour you
52// could use this. It's also handy for having different things tick in sync.
53//
54// Because we're ticking with the system clock the tick will likely not run for
55// the entire specified duration. For example, if we're ticking for one minute
56// and the clock is at 12:34:20 then the next tick will happen at 12:35:00, 40
57// seconds later.
58//
59// To produce the command, pass a duration and a function which returns
60// a message containing the time at which the tick occurred.
61//
62// type TickMsg time.Time
63//
64// cmd := Every(time.Second, func(t time.Time) Msg {
65// return TickMsg(t)
66// })
67//
68// Beginners' note: Every sends a single message and won't automatically
69// dispatch messages at an interval. To do that, you'll want to return another
70// Every command after receiving your tick message. For example:
71//
72// type TickMsg time.Time
73//
74// // Send a message every second.
75// func tickEvery() Cmd {
76// return Every(time.Second, func(t time.Time) Msg {
77// return TickMsg(t)
78// })
79// }
80//
81// func (m model) Init() (Model, Cmd) {
82// // Start ticking.
83// return m, tickEvery()
84// }
85//
86// func (m model) Update(msg Msg) (Model, Cmd) {
87// switch msg.(type) {
88// case TickMsg:
89// // Return your Every command again to loop.
90// return m, tickEvery()
91// }
92// return m, nil
93// }
94//
95// Every is analogous to Tick in the Elm Architecture.
96func Every(duration time.Duration, fn func(time.Time) Msg) Cmd {
97 n := time.Now()
98 d := n.Truncate(duration).Add(duration).Sub(n)
99 t := time.NewTimer(d)
100 return func() Msg {
101 ts := <-t.C
102 t.Stop()
103 for len(t.C) > 0 {
104 <-t.C
105 }
106 return fn(ts)
107 }
108}
109
110// Tick produces a command at an interval independent of the system clock at
111// the given duration. That is, the timer begins precisely when invoked,
112// and runs for its entire duration.
113//
114// To produce the command, pass a duration and a function which returns
115// a message containing the time at which the tick occurred.
116//
117// type TickMsg time.Time
118//
119// cmd := Tick(time.Second, func(t time.Time) Msg {
120// return TickMsg(t)
121// })
122//
123// Beginners' note: Tick sends a single message and won't automatically
124// dispatch messages at an interval. To do that, you'll want to return another
125// Tick command after receiving your tick message. For example:
126//
127// type TickMsg time.Time
128//
129// func doTick() Cmd {
130// return Tick(time.Second, func(t time.Time) Msg {
131// return TickMsg(t)
132// })
133// }
134//
135// func (m model) Init() (Model, Cmd) {
136// // Start ticking.
137// return m, doTick()
138// }
139//
140// func (m model) Update(msg Msg) (Model, Cmd) {
141// switch msg.(type) {
142// case TickMsg:
143// // Return your Tick command again to loop.
144// return m, doTick()
145// }
146// return m, nil
147// }
148func Tick(d time.Duration, fn func(time.Time) Msg) Cmd {
149 t := time.NewTimer(d)
150 return func() Msg {
151 ts := <-t.C
152 t.Stop()
153 for len(t.C) > 0 {
154 <-t.C
155 }
156 return fn(ts)
157 }
158}
159
160// setWindowTitleMsg is an internal message used to set the window title.
161type setWindowTitleMsg string
162
163// SetWindowTitle produces a command that sets the terminal title.
164//
165// For example:
166//
167// func (m model) Init() (Model, Cmd) {
168// // Set title.
169// return m, tea.SetWindowTitle("My App")
170// }
171func SetWindowTitle(title string) Cmd {
172 return func() Msg {
173 return setWindowTitleMsg(title)
174 }
175}
176
177type windowSizeMsg struct{}
178
179// RequestWindowSize is a command that queries the terminal for its current
180// size. It delivers the results to Update via a [WindowSizeMsg]. Keep in mind
181// that WindowSizeMsgs will automatically be delivered to Update when the
182// [Program] starts and when the window dimensions change so in many cases you
183// will not need to explicitly invoke this command.
184func RequestWindowSize() Msg {
185 return windowSizeMsg{}
186}