tea.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. // Package tea provides a framework for building rich terminal user interfaces
  2. // based on the paradigms of The Elm Architecture. It's well-suited for simple
  3. // and complex terminal applications, either inline, full-window, or a mix of
  4. // both. It's been battle-tested in several large projects and is
  5. // production-ready.
  6. //
  7. // A tutorial is available at https://github.com/charmbracelet/bubbletea/tree/master/tutorials
  8. //
  9. // Example programs can be found at https://github.com/charmbracelet/bubbletea/tree/master/examples
  10. package tea
  11. import (
  12. "context"
  13. "errors"
  14. "fmt"
  15. "io"
  16. "os"
  17. "os/signal"
  18. "runtime/debug"
  19. "sync"
  20. "sync/atomic"
  21. "syscall"
  22. "github.com/charmbracelet/x/term"
  23. "github.com/muesli/cancelreader"
  24. "golang.org/x/sync/errgroup"
  25. )
  26. // ErrProgramKilled is returned by [Program.Run] when the program got killed.
  27. var ErrProgramKilled = errors.New("program was killed")
  28. // Msg contain data from the result of a IO operation. Msgs trigger the update
  29. // function and, henceforth, the UI.
  30. type Msg interface{}
  31. // Model contains the program's state as well as its core functions.
  32. type Model interface {
  33. // Init is the first function that will be called. It returns an optional
  34. // initial command. To not perform an initial command return nil.
  35. Init() Cmd
  36. // Update is called when a message is received. Use it to inspect messages
  37. // and, in response, update the model and/or send a command.
  38. Update(Msg) (Model, Cmd)
  39. // View renders the program's UI, which is just a string. The view is
  40. // rendered after every Update.
  41. View() string
  42. }
  43. // Cmd is an IO operation that returns a message when it's complete. If it's
  44. // nil it's considered a no-op. Use it for things like HTTP requests, timers,
  45. // saving and loading from disk, and so on.
  46. //
  47. // Note that there's almost never a reason to use a command to send a message
  48. // to another part of your program. That can almost always be done in the
  49. // update function.
  50. type Cmd func() Msg
  51. type inputType int
  52. const (
  53. defaultInput inputType = iota
  54. ttyInput
  55. customInput
  56. )
  57. // String implements the stringer interface for [inputType]. It is inteded to
  58. // be used in testing.
  59. func (i inputType) String() string {
  60. return [...]string{
  61. "default input",
  62. "tty input",
  63. "custom input",
  64. }[i]
  65. }
  66. // Options to customize the program during its initialization. These are
  67. // generally set with ProgramOptions.
  68. //
  69. // The options here are treated as bits.
  70. type startupOptions int16
  71. func (s startupOptions) has(option startupOptions) bool {
  72. return s&option != 0
  73. }
  74. const (
  75. withAltScreen startupOptions = 1 << iota
  76. withMouseCellMotion
  77. withMouseAllMotion
  78. withANSICompressor
  79. withoutSignalHandler
  80. // Catching panics is incredibly useful for restoring the terminal to a
  81. // usable state after a panic occurs. When this is set, Bubble Tea will
  82. // recover from panics, print the stack trace, and disable raw mode. This
  83. // feature is on by default.
  84. withoutCatchPanics
  85. withoutBracketedPaste
  86. )
  87. // channelHandlers manages the series of channels returned by various processes.
  88. // It allows us to wait for those processes to terminate before exiting the
  89. // program.
  90. type channelHandlers []chan struct{}
  91. // Adds a channel to the list of handlers. We wait for all handlers to terminate
  92. // gracefully on shutdown.
  93. func (h *channelHandlers) add(ch chan struct{}) {
  94. *h = append(*h, ch)
  95. }
  96. // shutdown waits for all handlers to terminate.
  97. func (h channelHandlers) shutdown() {
  98. var wg sync.WaitGroup
  99. for _, ch := range h {
  100. wg.Add(1)
  101. go func(ch chan struct{}) {
  102. <-ch
  103. wg.Done()
  104. }(ch)
  105. }
  106. wg.Wait()
  107. }
  108. // Program is a terminal user interface.
  109. type Program struct {
  110. initialModel Model
  111. // Configuration options that will set as the program is initializing,
  112. // treated as bits. These options can be set via various ProgramOptions.
  113. startupOptions startupOptions
  114. // startupTitle is the title that will be set on the terminal when the
  115. // program starts.
  116. startupTitle string
  117. inputType inputType
  118. ctx context.Context
  119. cancel context.CancelFunc
  120. msgs chan Msg
  121. errs chan error
  122. finished chan struct{}
  123. // where to send output, this will usually be os.Stdout.
  124. output io.Writer
  125. // ttyOutput is null if output is not a TTY.
  126. ttyOutput term.File
  127. previousOutputState *term.State
  128. renderer renderer
  129. // where to read inputs from, this will usually be os.Stdin.
  130. input io.Reader
  131. // ttyInput is null if input is not a TTY.
  132. ttyInput term.File
  133. previousTtyInputState *term.State
  134. cancelReader cancelreader.CancelReader
  135. readLoopDone chan struct{}
  136. // was the altscreen active before releasing the terminal?
  137. altScreenWasActive bool
  138. ignoreSignals uint32
  139. bpWasActive bool // was the bracketed paste mode active before releasing the terminal?
  140. filter func(Model, Msg) Msg
  141. // fps is the frames per second we should set on the renderer, if
  142. // applicable,
  143. fps int
  144. }
  145. // Quit is a special command that tells the Bubble Tea program to exit.
  146. func Quit() Msg {
  147. return QuitMsg{}
  148. }
  149. // QuitMsg signals that the program should quit. You can send a QuitMsg with
  150. // Quit.
  151. type QuitMsg struct{}
  152. // NewProgram creates a new Program.
  153. func NewProgram(model Model, opts ...ProgramOption) *Program {
  154. p := &Program{
  155. initialModel: model,
  156. msgs: make(chan Msg),
  157. }
  158. // Apply all options to the program.
  159. for _, opt := range opts {
  160. opt(p)
  161. }
  162. // A context can be provided with a ProgramOption, but if none was provided
  163. // we'll use the default background context.
  164. if p.ctx == nil {
  165. p.ctx = context.Background()
  166. }
  167. // Initialize context and teardown channel.
  168. p.ctx, p.cancel = context.WithCancel(p.ctx)
  169. // if no output was set, set it to stdout
  170. if p.output == nil {
  171. p.output = os.Stdout
  172. }
  173. return p
  174. }
  175. func (p *Program) handleSignals() chan struct{} {
  176. ch := make(chan struct{})
  177. // Listen for SIGINT and SIGTERM.
  178. //
  179. // In most cases ^C will not send an interrupt because the terminal will be
  180. // in raw mode and ^C will be captured as a keystroke and sent along to
  181. // Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be
  182. // caught here.
  183. //
  184. // SIGTERM is sent by unix utilities (like kill) to terminate a process.
  185. go func() {
  186. sig := make(chan os.Signal, 1)
  187. signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
  188. defer func() {
  189. signal.Stop(sig)
  190. close(ch)
  191. }()
  192. for {
  193. select {
  194. case <-p.ctx.Done():
  195. return
  196. case <-sig:
  197. if atomic.LoadUint32(&p.ignoreSignals) == 0 {
  198. p.msgs <- QuitMsg{}
  199. return
  200. }
  201. }
  202. }
  203. }()
  204. return ch
  205. }
  206. // handleResize handles terminal resize events.
  207. func (p *Program) handleResize() chan struct{} {
  208. ch := make(chan struct{})
  209. if p.ttyOutput != nil {
  210. // Get the initial terminal size and send it to the program.
  211. go p.checkResize()
  212. // Listen for window resizes.
  213. go p.listenForResize(ch)
  214. } else {
  215. close(ch)
  216. }
  217. return ch
  218. }
  219. // handleCommands runs commands in a goroutine and sends the result to the
  220. // program's message channel.
  221. func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
  222. ch := make(chan struct{})
  223. go func() {
  224. defer close(ch)
  225. for {
  226. select {
  227. case <-p.ctx.Done():
  228. return
  229. case cmd := <-cmds:
  230. if cmd == nil {
  231. continue
  232. }
  233. // Don't wait on these goroutines, otherwise the shutdown
  234. // latency would get too large as a Cmd can run for some time
  235. // (e.g. tick commands that sleep for half a second). It's not
  236. // possible to cancel them so we'll have to leak the goroutine
  237. // until Cmd returns.
  238. go func() {
  239. msg := cmd() // this can be long.
  240. p.Send(msg)
  241. }()
  242. }
  243. }
  244. }()
  245. return ch
  246. }
  247. func (p *Program) disableMouse() {
  248. p.renderer.disableMouseCellMotion()
  249. p.renderer.disableMouseAllMotion()
  250. p.renderer.disableMouseSGRMode()
  251. }
  252. // eventLoop is the central message loop. It receives and handles the default
  253. // Bubble Tea messages, update the model and triggers redraws.
  254. func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
  255. for {
  256. select {
  257. case <-p.ctx.Done():
  258. return model, nil
  259. case err := <-p.errs:
  260. return model, err
  261. case msg := <-p.msgs:
  262. // Filter messages.
  263. if p.filter != nil {
  264. msg = p.filter(model, msg)
  265. }
  266. if msg == nil {
  267. continue
  268. }
  269. // Handle special internal messages.
  270. switch msg := msg.(type) {
  271. case QuitMsg:
  272. return model, nil
  273. case clearScreenMsg:
  274. p.renderer.clearScreen()
  275. case enterAltScreenMsg:
  276. p.renderer.enterAltScreen()
  277. case exitAltScreenMsg:
  278. p.renderer.exitAltScreen()
  279. case enableMouseCellMotionMsg, enableMouseAllMotionMsg:
  280. switch msg.(type) {
  281. case enableMouseCellMotionMsg:
  282. p.renderer.enableMouseCellMotion()
  283. case enableMouseAllMotionMsg:
  284. p.renderer.enableMouseAllMotion()
  285. }
  286. // mouse mode (1006) is a no-op if the terminal doesn't support it.
  287. p.renderer.enableMouseSGRMode()
  288. case disableMouseMsg:
  289. p.disableMouse()
  290. case showCursorMsg:
  291. p.renderer.showCursor()
  292. case hideCursorMsg:
  293. p.renderer.hideCursor()
  294. case enableBracketedPasteMsg:
  295. p.renderer.enableBracketedPaste()
  296. case disableBracketedPasteMsg:
  297. p.renderer.disableBracketedPaste()
  298. case execMsg:
  299. // NB: this blocks.
  300. p.exec(msg.cmd, msg.fn)
  301. case BatchMsg:
  302. for _, cmd := range msg {
  303. cmds <- cmd
  304. }
  305. continue
  306. case sequenceMsg:
  307. go func() {
  308. // Execute commands one at a time, in order.
  309. for _, cmd := range msg {
  310. if cmd == nil {
  311. continue
  312. }
  313. msg := cmd()
  314. if batchMsg, ok := msg.(BatchMsg); ok {
  315. g, _ := errgroup.WithContext(p.ctx)
  316. for _, cmd := range batchMsg {
  317. cmd := cmd
  318. g.Go(func() error {
  319. p.Send(cmd())
  320. return nil
  321. })
  322. }
  323. //nolint:errcheck
  324. g.Wait() // wait for all commands from batch msg to finish
  325. continue
  326. }
  327. p.Send(msg)
  328. }
  329. }()
  330. case setWindowTitleMsg:
  331. p.SetWindowTitle(string(msg))
  332. }
  333. // Process internal messages for the renderer.
  334. if r, ok := p.renderer.(*standardRenderer); ok {
  335. r.handleMessages(msg)
  336. }
  337. var cmd Cmd
  338. model, cmd = model.Update(msg) // run update
  339. cmds <- cmd // process command (if any)
  340. p.renderer.write(model.View()) // send view to renderer
  341. }
  342. }
  343. }
  344. // Run initializes the program and runs its event loops, blocking until it gets
  345. // terminated by either [Program.Quit], [Program.Kill], or its signal handler.
  346. // Returns the final model.
  347. func (p *Program) Run() (Model, error) {
  348. handlers := channelHandlers{}
  349. cmds := make(chan Cmd)
  350. p.errs = make(chan error)
  351. p.finished = make(chan struct{}, 1)
  352. defer p.cancel()
  353. switch p.inputType {
  354. case defaultInput:
  355. p.input = os.Stdin
  356. // The user has not set a custom input, so we need to check whether or
  357. // not standard input is a terminal. If it's not, we open a new TTY for
  358. // input. This will allow things to "just work" in cases where data was
  359. // piped in or redirected to the application.
  360. //
  361. // To disable input entirely pass nil to the [WithInput] program option.
  362. f, isFile := p.input.(term.File)
  363. if !isFile {
  364. break
  365. }
  366. if term.IsTerminal(f.Fd()) {
  367. break
  368. }
  369. f, err := openInputTTY()
  370. if err != nil {
  371. return p.initialModel, err
  372. }
  373. defer f.Close() //nolint:errcheck
  374. p.input = f
  375. case ttyInput:
  376. // Open a new TTY, by request
  377. f, err := openInputTTY()
  378. if err != nil {
  379. return p.initialModel, err
  380. }
  381. defer f.Close() //nolint:errcheck
  382. p.input = f
  383. case customInput:
  384. // (There is nothing extra to do.)
  385. }
  386. // Handle signals.
  387. if !p.startupOptions.has(withoutSignalHandler) {
  388. handlers.add(p.handleSignals())
  389. }
  390. // Recover from panics.
  391. if !p.startupOptions.has(withoutCatchPanics) {
  392. defer func() {
  393. if r := recover(); r != nil {
  394. p.shutdown(true)
  395. fmt.Printf("Caught panic:\n\n%s\n\nRestoring terminal...\n\n", r)
  396. debug.PrintStack()
  397. return
  398. }
  399. }()
  400. }
  401. // If no renderer is set use the standard one.
  402. if p.renderer == nil {
  403. p.renderer = newRenderer(p.output, p.startupOptions.has(withANSICompressor), p.fps)
  404. }
  405. // Check if output is a TTY before entering raw mode, hiding the cursor and
  406. // so on.
  407. if err := p.initTerminal(); err != nil {
  408. return p.initialModel, err
  409. }
  410. // Honor program startup options.
  411. if p.startupTitle != "" {
  412. p.renderer.setWindowTitle(p.startupTitle)
  413. }
  414. if p.startupOptions&withAltScreen != 0 {
  415. p.renderer.enterAltScreen()
  416. }
  417. if p.startupOptions&withoutBracketedPaste == 0 {
  418. p.renderer.enableBracketedPaste()
  419. }
  420. if p.startupOptions&withMouseCellMotion != 0 {
  421. p.renderer.enableMouseCellMotion()
  422. p.renderer.enableMouseSGRMode()
  423. } else if p.startupOptions&withMouseAllMotion != 0 {
  424. p.renderer.enableMouseAllMotion()
  425. p.renderer.enableMouseSGRMode()
  426. }
  427. // Start the renderer.
  428. p.renderer.start()
  429. // Initialize the program.
  430. model := p.initialModel
  431. if initCmd := model.Init(); initCmd != nil {
  432. ch := make(chan struct{})
  433. handlers.add(ch)
  434. go func() {
  435. defer close(ch)
  436. select {
  437. case cmds <- initCmd:
  438. case <-p.ctx.Done():
  439. }
  440. }()
  441. }
  442. // Render the initial view.
  443. p.renderer.write(model.View())
  444. // Subscribe to user input.
  445. if p.input != nil {
  446. if err := p.initCancelReader(); err != nil {
  447. return model, err
  448. }
  449. }
  450. // Handle resize events.
  451. handlers.add(p.handleResize())
  452. // Process commands.
  453. handlers.add(p.handleCommands(cmds))
  454. // Run event loop, handle updates and draw.
  455. model, err := p.eventLoop(model, cmds)
  456. killed := p.ctx.Err() != nil
  457. if killed {
  458. err = ErrProgramKilled
  459. } else {
  460. // Ensure we rendered the final state of the model.
  461. p.renderer.write(model.View())
  462. }
  463. // Tear down.
  464. p.cancel()
  465. // Check if the cancel reader has been setup before waiting and closing.
  466. if p.cancelReader != nil {
  467. // Wait for input loop to finish.
  468. if p.cancelReader.Cancel() {
  469. p.waitForReadLoop()
  470. }
  471. _ = p.cancelReader.Close()
  472. }
  473. // Wait for all handlers to finish.
  474. handlers.shutdown()
  475. // Restore terminal state.
  476. p.shutdown(killed)
  477. return model, err
  478. }
  479. // StartReturningModel initializes the program and runs its event loops,
  480. // blocking until it gets terminated by either [Program.Quit], [Program.Kill],
  481. // or its signal handler. Returns the final model.
  482. //
  483. // Deprecated: please use [Program.Run] instead.
  484. func (p *Program) StartReturningModel() (Model, error) {
  485. return p.Run()
  486. }
  487. // Start initializes the program and runs its event loops, blocking until it
  488. // gets terminated by either [Program.Quit], [Program.Kill], or its signal
  489. // handler.
  490. //
  491. // Deprecated: please use [Program.Run] instead.
  492. func (p *Program) Start() error {
  493. _, err := p.Run()
  494. return err
  495. }
  496. // Send sends a message to the main update function, effectively allowing
  497. // messages to be injected from outside the program for interoperability
  498. // purposes.
  499. //
  500. // If the program hasn't started yet this will be a blocking operation.
  501. // If the program has already been terminated this will be a no-op, so it's safe
  502. // to send messages after the program has exited.
  503. func (p *Program) Send(msg Msg) {
  504. select {
  505. case <-p.ctx.Done():
  506. case p.msgs <- msg:
  507. }
  508. }
  509. // Quit is a convenience function for quitting Bubble Tea programs. Use it
  510. // when you need to shut down a Bubble Tea program from the outside.
  511. //
  512. // If you wish to quit from within a Bubble Tea program use the Quit command.
  513. //
  514. // If the program is not running this will be a no-op, so it's safe to call
  515. // if the program is unstarted or has already exited.
  516. func (p *Program) Quit() {
  517. p.Send(Quit())
  518. }
  519. // Kill stops the program immediately and restores the former terminal state.
  520. // The final render that you would normally see when quitting will be skipped.
  521. // [program.Run] returns a [ErrProgramKilled] error.
  522. func (p *Program) Kill() {
  523. p.cancel()
  524. }
  525. // Wait waits/blocks until the underlying Program finished shutting down.
  526. func (p *Program) Wait() {
  527. <-p.finished
  528. }
  529. // shutdown performs operations to free up resources and restore the terminal
  530. // to its original state.
  531. func (p *Program) shutdown(kill bool) {
  532. if p.renderer != nil {
  533. if kill {
  534. p.renderer.kill()
  535. } else {
  536. p.renderer.stop()
  537. }
  538. }
  539. _ = p.restoreTerminalState()
  540. p.finished <- struct{}{}
  541. }
  542. // ReleaseTerminal restores the original terminal state and cancels the input
  543. // reader. You can return control to the Program with RestoreTerminal.
  544. func (p *Program) ReleaseTerminal() error {
  545. atomic.StoreUint32(&p.ignoreSignals, 1)
  546. if p.cancelReader != nil {
  547. p.cancelReader.Cancel()
  548. }
  549. p.waitForReadLoop()
  550. if p.renderer != nil {
  551. p.renderer.stop()
  552. p.altScreenWasActive = p.renderer.altScreen()
  553. p.bpWasActive = p.renderer.bracketedPasteActive()
  554. }
  555. return p.restoreTerminalState()
  556. }
  557. // RestoreTerminal reinitializes the Program's input reader, restores the
  558. // terminal to the former state when the program was running, and repaints.
  559. // Use it to reinitialize a Program after running ReleaseTerminal.
  560. func (p *Program) RestoreTerminal() error {
  561. atomic.StoreUint32(&p.ignoreSignals, 0)
  562. if err := p.initTerminal(); err != nil {
  563. return err
  564. }
  565. if err := p.initCancelReader(); err != nil {
  566. return err
  567. }
  568. if p.altScreenWasActive {
  569. p.renderer.enterAltScreen()
  570. } else {
  571. // entering alt screen already causes a repaint.
  572. go p.Send(repaintMsg{})
  573. }
  574. if p.renderer != nil {
  575. p.renderer.start()
  576. }
  577. if p.bpWasActive {
  578. p.renderer.enableBracketedPaste()
  579. }
  580. // If the output is a terminal, it may have been resized while another
  581. // process was at the foreground, in which case we may not have received
  582. // SIGWINCH. Detect any size change now and propagate the new size as
  583. // needed.
  584. go p.checkResize()
  585. return nil
  586. }
  587. // Println prints above the Program. This output is unmanaged by the program
  588. // and will persist across renders by the Program.
  589. //
  590. // If the altscreen is active no output will be printed.
  591. func (p *Program) Println(args ...interface{}) {
  592. p.msgs <- printLineMessage{
  593. messageBody: fmt.Sprint(args...),
  594. }
  595. }
  596. // Printf prints above the Program. It takes a format template followed by
  597. // values similar to fmt.Printf. This output is unmanaged by the program and
  598. // will persist across renders by the Program.
  599. //
  600. // Unlike fmt.Printf (but similar to log.Printf) the message will be print on
  601. // its own line.
  602. //
  603. // If the altscreen is active no output will be printed.
  604. func (p *Program) Printf(template string, args ...interface{}) {
  605. p.msgs <- printLineMessage{
  606. messageBody: fmt.Sprintf(template, args...),
  607. }
  608. }