Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/chzyer/readline/terminal.go
2875 views
1
package readline
2
3
import (
4
"bufio"
5
"fmt"
6
"io"
7
"strings"
8
"sync"
9
"sync/atomic"
10
)
11
12
type Terminal struct {
13
m sync.Mutex
14
cfg *Config
15
outchan chan rune
16
closed int32
17
stopChan chan struct{}
18
kickChan chan struct{}
19
wg sync.WaitGroup
20
isReading int32
21
sleeping int32
22
23
sizeChan chan string
24
}
25
26
func NewTerminal(cfg *Config) (*Terminal, error) {
27
if err := cfg.Init(); err != nil {
28
return nil, err
29
}
30
t := &Terminal{
31
cfg: cfg,
32
kickChan: make(chan struct{}, 1),
33
outchan: make(chan rune),
34
stopChan: make(chan struct{}, 1),
35
sizeChan: make(chan string, 1),
36
}
37
38
go t.ioloop()
39
return t, nil
40
}
41
42
// SleepToResume will sleep myself, and return only if I'm resumed.
43
func (t *Terminal) SleepToResume() {
44
if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
45
return
46
}
47
defer atomic.StoreInt32(&t.sleeping, 0)
48
49
t.ExitRawMode()
50
ch := WaitForResume()
51
SuspendMe()
52
<-ch
53
t.EnterRawMode()
54
}
55
56
func (t *Terminal) EnterRawMode() (err error) {
57
return t.cfg.FuncMakeRaw()
58
}
59
60
func (t *Terminal) ExitRawMode() (err error) {
61
return t.cfg.FuncExitRaw()
62
}
63
64
func (t *Terminal) Write(b []byte) (int, error) {
65
return t.cfg.Stdout.Write(b)
66
}
67
68
// WriteStdin prefill the next Stdin fetch
69
// Next time you call ReadLine() this value will be writen before the user input
70
func (t *Terminal) WriteStdin(b []byte) (int, error) {
71
return t.cfg.StdinWriter.Write(b)
72
}
73
74
type termSize struct {
75
left int
76
top int
77
}
78
79
func (t *Terminal) GetOffset(f func(offset string)) {
80
go func() {
81
f(<-t.sizeChan)
82
}()
83
t.Write([]byte("\033[6n"))
84
}
85
86
func (t *Terminal) Print(s string) {
87
fmt.Fprintf(t.cfg.Stdout, "%s", s)
88
}
89
90
func (t *Terminal) PrintRune(r rune) {
91
fmt.Fprintf(t.cfg.Stdout, "%c", r)
92
}
93
94
func (t *Terminal) Readline() *Operation {
95
return NewOperation(t, t.cfg)
96
}
97
98
// return rune(0) if meet EOF
99
func (t *Terminal) ReadRune() rune {
100
ch, ok := <-t.outchan
101
if !ok {
102
return rune(0)
103
}
104
return ch
105
}
106
107
func (t *Terminal) IsReading() bool {
108
return atomic.LoadInt32(&t.isReading) == 1
109
}
110
111
func (t *Terminal) KickRead() {
112
select {
113
case t.kickChan <- struct{}{}:
114
default:
115
}
116
}
117
118
func (t *Terminal) ioloop() {
119
t.wg.Add(1)
120
defer func() {
121
t.wg.Done()
122
close(t.outchan)
123
}()
124
125
var (
126
isEscape bool
127
isEscapeEx bool
128
isEscapeSS3 bool
129
expectNextChar bool
130
)
131
132
buf := bufio.NewReader(t.getStdin())
133
for {
134
if !expectNextChar {
135
atomic.StoreInt32(&t.isReading, 0)
136
select {
137
case <-t.kickChan:
138
atomic.StoreInt32(&t.isReading, 1)
139
case <-t.stopChan:
140
return
141
}
142
}
143
expectNextChar = false
144
r, _, err := buf.ReadRune()
145
if err != nil {
146
if strings.Contains(err.Error(), "interrupted system call") {
147
expectNextChar = true
148
continue
149
}
150
break
151
}
152
153
if isEscape {
154
isEscape = false
155
if r == CharEscapeEx {
156
// ^][
157
expectNextChar = true
158
isEscapeEx = true
159
continue
160
} else if r == CharO {
161
// ^]O
162
expectNextChar = true
163
isEscapeSS3 = true
164
continue
165
}
166
r = escapeKey(r, buf)
167
} else if isEscapeEx {
168
isEscapeEx = false
169
if key := readEscKey(r, buf); key != nil {
170
r = escapeExKey(key)
171
// offset
172
if key.typ == 'R' {
173
if _, _, ok := key.Get2(); ok {
174
select {
175
case t.sizeChan <- key.attr:
176
default:
177
}
178
}
179
expectNextChar = true
180
continue
181
}
182
}
183
if r == 0 {
184
expectNextChar = true
185
continue
186
}
187
} else if isEscapeSS3 {
188
isEscapeSS3 = false
189
if key := readEscKey(r, buf); key != nil {
190
r = escapeSS3Key(key)
191
}
192
if r == 0 {
193
expectNextChar = true
194
continue
195
}
196
}
197
198
expectNextChar = true
199
switch r {
200
case CharEsc:
201
if t.cfg.VimMode {
202
t.outchan <- r
203
break
204
}
205
isEscape = true
206
case CharInterrupt, CharEnter, CharCtrlJ, CharDelete:
207
expectNextChar = false
208
fallthrough
209
default:
210
t.outchan <- r
211
}
212
}
213
214
}
215
216
func (t *Terminal) Bell() {
217
fmt.Fprintf(t, "%c", CharBell)
218
}
219
220
func (t *Terminal) Close() error {
221
if atomic.SwapInt32(&t.closed, 1) != 0 {
222
return nil
223
}
224
if closer, ok := t.cfg.Stdin.(io.Closer); ok {
225
closer.Close()
226
}
227
close(t.stopChan)
228
t.wg.Wait()
229
return t.ExitRawMode()
230
}
231
232
func (t *Terminal) GetConfig() *Config {
233
t.m.Lock()
234
cfg := *t.cfg
235
t.m.Unlock()
236
return &cfg
237
}
238
239
func (t *Terminal) getStdin() io.Reader {
240
t.m.Lock()
241
r := t.cfg.Stdin
242
t.m.Unlock()
243
return r
244
}
245
246
func (t *Terminal) SetConfig(c *Config) error {
247
if err := c.Init(); err != nil {
248
return err
249
}
250
t.m.Lock()
251
t.cfg = c
252
t.m.Unlock()
253
return nil
254
}
255
256