tty.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package tea
  2. import (
  3. "errors"
  4. "io"
  5. "os"
  6. "time"
  7. isatty "github.com/mattn/go-isatty"
  8. "github.com/muesli/cancelreader"
  9. "golang.org/x/term"
  10. )
  11. func (p *Program) initTerminal() error {
  12. err := p.initInput()
  13. if err != nil {
  14. return err
  15. }
  16. if p.console != nil {
  17. err = p.console.SetRaw()
  18. if err != nil {
  19. return err
  20. }
  21. }
  22. p.renderer.hideCursor()
  23. return nil
  24. }
  25. // restoreTerminalState restores the terminal to the state prior to running the
  26. // Bubble Tea program.
  27. func (p *Program) restoreTerminalState() error {
  28. if p.renderer != nil {
  29. p.renderer.showCursor()
  30. p.renderer.disableMouseCellMotion()
  31. p.renderer.disableMouseAllMotion()
  32. if p.renderer.altScreen() {
  33. p.renderer.exitAltScreen()
  34. // give the terminal a moment to catch up
  35. time.Sleep(time.Millisecond * 10)
  36. }
  37. }
  38. if p.console != nil {
  39. err := p.console.Reset()
  40. if err != nil {
  41. return err
  42. }
  43. }
  44. return p.restoreInput()
  45. }
  46. // initCancelReader (re)commences reading inputs.
  47. func (p *Program) initCancelReader() error {
  48. var err error
  49. p.cancelReader, err = cancelreader.NewReader(p.input)
  50. if err != nil {
  51. return err
  52. }
  53. p.readLoopDone = make(chan struct{})
  54. go p.readLoop()
  55. return nil
  56. }
  57. func (p *Program) readLoop() {
  58. defer close(p.readLoopDone)
  59. for {
  60. if p.ctx.Err() != nil {
  61. return
  62. }
  63. msgs, err := readInputs(p.cancelReader)
  64. if err != nil {
  65. if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
  66. select {
  67. case <-p.ctx.Done():
  68. case p.errs <- err:
  69. }
  70. }
  71. return
  72. }
  73. for _, msg := range msgs {
  74. p.msgs <- msg
  75. }
  76. }
  77. }
  78. // waitForReadLoop waits for the cancelReader to finish its read loop.
  79. func (p *Program) waitForReadLoop() {
  80. select {
  81. case <-p.readLoopDone:
  82. case <-time.After(500 * time.Millisecond):
  83. // The read loop hangs, which means the input
  84. // cancelReader's cancel function has returned true even
  85. // though it was not able to cancel the read.
  86. }
  87. }
  88. // checkResize detects the current size of the output and informs the program
  89. // via a WindowSizeMsg.
  90. func (p *Program) checkResize() {
  91. f, ok := p.output.TTY().(*os.File)
  92. if !ok || !isatty.IsTerminal(f.Fd()) {
  93. // can't query window size
  94. return
  95. }
  96. w, h, err := term.GetSize(int(f.Fd()))
  97. if err != nil {
  98. select {
  99. case <-p.ctx.Done():
  100. case p.errs <- err:
  101. }
  102. return
  103. }
  104. p.Send(WindowSizeMsg{
  105. Width: w,
  106. Height: h,
  107. })
  108. }