commands.go

  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}