main
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}