tty.go 2.5 KB

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