main
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() Cmd {
13// return 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() Cmd {
82// // Start ticking.
83// return 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() Cmd {
136// // Start ticking.
137// return 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// Sequentially produces a command that sequentially executes the given
161// commands.
162// The Msg returned is the first non-nil message returned by a Cmd.
163//
164// func saveStateCmd() Msg {
165// if err := save(); err != nil {
166// return errMsg{err}
167// }
168// return nil
169// }
170//
171// cmd := Sequentially(saveStateCmd, Quit)
172//
173// Deprecated: use Sequence instead.
174func Sequentially(cmds ...Cmd) Cmd {
175 return func() Msg {
176 for _, cmd := range cmds {
177 if cmd == nil {
178 continue
179 }
180 if msg := cmd(); msg != nil {
181 return msg
182 }
183 }
184 return nil
185 }
186}
187
188// setWindowTitleMsg is an internal message used to set the window title.
189type setWindowTitleMsg string
190
191// SetWindowTitle produces a command that sets the terminal title.
192//
193// For example:
194//
195// func (m model) Init() Cmd {
196// // Set title.
197// return tea.SetWindowTitle("My App")
198// }
199func SetWindowTitle(title string) Cmd {
200 return func() Msg {
201 return setWindowTitleMsg(title)
202 }
203}
204
205type windowSizeMsg struct{}
206
207// WindowSize is a command that queries the terminal for its current size. It
208// delivers the results to Update via a [WindowSizeMsg]. Keep in mind that
209// WindowSizeMsgs will automatically be delivered to Update when the [Program]
210// starts and when the window dimensions change so in many cases you will not
211// need to explicitly invoke this command.
212func WindowSize() Cmd {
213 return func() Msg {
214 return windowSizeMsg{}
215 }
216}