package tea import ( "errors" "fmt" "io" "time" "github.com/charmbracelet/x/term" "github.com/muesli/cancelreader" ) func (p *Program) suspend() { if err := p.ReleaseTerminal(); err != nil { // If we can't release input, abort. return } suspendProcess() _ = p.RestoreTerminal() go p.Send(ResumeMsg{}) } func (p *Program) initTerminal() error { if _, ok := p.renderer.(*nilRenderer); ok { // No need to initialize the terminal if we're not rendering return nil } if err := p.initInput(); err != nil { return err } p.renderer.hideCursor() return nil } // restoreTerminalState restores the terminal to the state prior to running the // Bubble Tea program. func (p *Program) restoreTerminalState() error { if p.renderer != nil { p.renderer.disableBracketedPaste() p.renderer.showCursor() p.disableMouse() if p.renderer.reportFocus() { p.renderer.disableReportFocus() } if p.renderer.altScreen() { p.renderer.exitAltScreen() // give the terminal a moment to catch up time.Sleep(time.Millisecond * 10) //nolint:gomnd } } return p.restoreInput() } // restoreInput restores the tty input to its original state. func (p *Program) restoreInput() error { if p.ttyInput != nil && p.previousTtyInputState != nil { if err := term.Restore(p.ttyInput.Fd(), p.previousTtyInputState); err != nil { return fmt.Errorf("error restoring console: %w", err) } } if p.ttyOutput != nil && p.previousOutputState != nil { if err := term.Restore(p.ttyOutput.Fd(), p.previousOutputState); err != nil { return fmt.Errorf("error restoring console: %w", err) } } return nil } // initCancelReader (re)commences reading inputs. func (p *Program) initCancelReader() error { var err error p.cancelReader, err = newInputReader(p.input) if err != nil { return fmt.Errorf("error creating cancelreader: %w", err) } p.readLoopDone = make(chan struct{}) go p.readLoop() return nil } func (p *Program) readLoop() { defer close(p.readLoopDone) err := readInputs(p.ctx, p.msgs, p.cancelReader) if !errors.Is(err, io.EOF) && !errors.Is(err, cancelreader.ErrCanceled) { select { case <-p.ctx.Done(): case p.errs <- err: } } } // waitForReadLoop waits for the cancelReader to finish its read loop. func (p *Program) waitForReadLoop() { select { case <-p.readLoopDone: case <-time.After(500 * time.Millisecond): //nolint:gomnd // The read loop hangs, which means the input // cancelReader's cancel function has returned true even // though it was not able to cancel the read. } } // checkResize detects the current size of the output and informs the program // via a WindowSizeMsg. func (p *Program) checkResize() { if p.ttyOutput == nil { // can't query window size return } w, h, err := term.GetSize(p.ttyOutput.Fd()) if err != nil { select { case <-p.ctx.Done(): case p.errs <- err: } return } p.Send(WindowSizeMsg{ Width: w, Height: h, }) }