main
Raw Download raw file
  1package tea
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7	"time"
  8
  9	"github.com/charmbracelet/x/term"
 10	"github.com/muesli/cancelreader"
 11)
 12
 13func (p *Program) suspend() {
 14	if err := p.ReleaseTerminal(); err != nil {
 15		// If we can't release input, abort.
 16		return
 17	}
 18
 19	suspendProcess()
 20
 21	_ = p.RestoreTerminal()
 22	go p.Send(ResumeMsg{})
 23}
 24
 25func (p *Program) initTerminal() error {
 26	if _, ok := p.renderer.(*nilRenderer); ok {
 27		// No need to initialize the terminal if we're not rendering
 28		return nil
 29	}
 30
 31	if err := p.initInput(); err != nil {
 32		return err
 33	}
 34
 35	p.renderer.hideCursor()
 36	return nil
 37}
 38
 39// restoreTerminalState restores the terminal to the state prior to running the
 40// Bubble Tea program.
 41func (p *Program) restoreTerminalState() error {
 42	if p.renderer != nil {
 43		p.renderer.disableBracketedPaste()
 44		p.renderer.showCursor()
 45		p.disableMouse()
 46
 47		if p.renderer.reportFocus() {
 48			p.renderer.disableReportFocus()
 49		}
 50
 51		if p.renderer.altScreen() {
 52			p.renderer.exitAltScreen()
 53
 54			// give the terminal a moment to catch up
 55			time.Sleep(time.Millisecond * 10) //nolint:gomnd
 56		}
 57	}
 58
 59	return p.restoreInput()
 60}
 61
 62// restoreInput restores the tty input to its original state.
 63func (p *Program) restoreInput() error {
 64	if p.ttyInput != nil && p.previousTtyInputState != nil {
 65		if err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil {
 66			return fmt.Errorf("error restoring console: %w", err)
 67		}
 68	}
 69	if p.ttyOutput != nil && p.previousOutputState != nil {
 70		if err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil {
 71			return fmt.Errorf("error restoring console: %w", err)
 72		}
 73	}
 74	return nil
 75}
 76
 77// initCancelReader (re)commences reading inputs.
 78func (p *Program) initCancelReader(cancel bool) error {
 79	if cancel && p.cancelReader != nil {
 80		p.cancelReader.Cancel()
 81		p.waitForReadLoop()
 82	}
 83
 84	var err error
 85	p.cancelReader, err = newInputReader(p.input, p.mouseMode)
 86	if err != nil {
 87		return fmt.Errorf("error creating cancelreader: %w", err)
 88	}
 89
 90	p.readLoopDone = make(chan struct{})
 91	go p.readLoop()
 92
 93	return nil
 94}
 95
 96func (p *Program) readLoop() {
 97	defer close(p.readLoopDone)
 98
 99	err := readInputs(p.ctx, p.msgs, p.cancelReader)
100	if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) {
101		select {
102		case <-p.ctx.Done():
103		case p.errs <- err:
104		}
105	}
106}
107
108// waitForReadLoop waits for the cancelReader to finish its read loop.
109func (p *Program) waitForReadLoop() {
110	select {
111	case <-p.readLoopDone:
112	case <-time.After(500 * time.Millisecond): //nolint:gomnd
113		// The read loop hangs, which means the input
114		// cancelReader's cancel function has returned true even
115		// though it was not able to cancel the read.
116	}
117}
118
119// checkResize detects the current size of the output and informs the program
120// via a WindowSizeMsg.
121func (p *Program) checkResize() {
122	if p.ttyOutput == nil {
123		// can't query window size
124		return
125	}
126
127	w, h, err := term.GetSize(p.ttyOutput.Fd())
128	if err != nil {
129		select {
130		case <-p.ctx.Done():
131		case p.errs <- err:
132		}
133
134		return
135	}
136
137	p.Send(WindowSizeMsg{
138		Width:  w,
139		Height: h,
140	})
141}