commands.go 5.0 KB

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