tea.go 20 KB

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