tty.go 2.4 KB

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