Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/chzyer/readline/complete.go
2875 views
1
package readline
2
3
import (
4
"bufio"
5
"bytes"
6
"fmt"
7
"io"
8
)
9
10
type AutoCompleter interface {
11
// Readline will pass the whole line and current offset to it
12
// Completer need to pass all the candidates, and how long they shared the same characters in line
13
// Example:
14
// [go, git, git-shell, grep]
15
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
16
// Do("gi", 2) => ["t", "t-shell"], 2
17
// Do("git", 3) => ["", "-shell"], 3
18
Do(line []rune, pos int) (newLine [][]rune, length int)
19
}
20
21
type TabCompleter struct{}
22
23
func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
24
return [][]rune{[]rune("\t")}, 0
25
}
26
27
type opCompleter struct {
28
w io.Writer
29
op *Operation
30
width int
31
32
inCompleteMode bool
33
inSelectMode bool
34
candidate [][]rune
35
candidateSource []rune
36
candidateOff int
37
candidateChoise int
38
candidateColNum int
39
}
40
41
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
42
return &opCompleter{
43
w: w,
44
op: op,
45
width: width,
46
}
47
}
48
49
func (o *opCompleter) doSelect() {
50
if len(o.candidate) == 1 {
51
o.op.buf.WriteRunes(o.candidate[0])
52
o.ExitCompleteMode(false)
53
return
54
}
55
o.nextCandidate(1)
56
o.CompleteRefresh()
57
}
58
59
func (o *opCompleter) nextCandidate(i int) {
60
o.candidateChoise += i
61
o.candidateChoise = o.candidateChoise % len(o.candidate)
62
if o.candidateChoise < 0 {
63
o.candidateChoise = len(o.candidate) + o.candidateChoise
64
}
65
}
66
67
func (o *opCompleter) OnComplete() bool {
68
if o.width == 0 {
69
return false
70
}
71
if o.IsInCompleteSelectMode() {
72
o.doSelect()
73
return true
74
}
75
76
buf := o.op.buf
77
rs := buf.Runes()
78
79
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
80
o.EnterCompleteSelectMode()
81
o.doSelect()
82
return true
83
}
84
85
o.ExitCompleteSelectMode()
86
o.candidateSource = rs
87
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
88
if len(newLines) == 0 {
89
o.ExitCompleteMode(false)
90
return true
91
}
92
93
// only Aggregate candidates in non-complete mode
94
if !o.IsInCompleteMode() {
95
if len(newLines) == 1 {
96
buf.WriteRunes(newLines[0])
97
o.ExitCompleteMode(false)
98
return true
99
}
100
101
same, size := runes.Aggregate(newLines)
102
if size > 0 {
103
buf.WriteRunes(same)
104
o.ExitCompleteMode(false)
105
return true
106
}
107
}
108
109
o.EnterCompleteMode(offset, newLines)
110
return true
111
}
112
113
func (o *opCompleter) IsInCompleteSelectMode() bool {
114
return o.inSelectMode
115
}
116
117
func (o *opCompleter) IsInCompleteMode() bool {
118
return o.inCompleteMode
119
}
120
121
func (o *opCompleter) HandleCompleteSelect(r rune) bool {
122
next := true
123
switch r {
124
case CharEnter, CharCtrlJ:
125
next = false
126
o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
127
o.ExitCompleteMode(false)
128
case CharLineStart:
129
num := o.candidateChoise % o.candidateColNum
130
o.nextCandidate(-num)
131
case CharLineEnd:
132
num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
133
o.candidateChoise += num
134
if o.candidateChoise >= len(o.candidate) {
135
o.candidateChoise = len(o.candidate) - 1
136
}
137
case CharBackspace:
138
o.ExitCompleteSelectMode()
139
next = false
140
case CharTab, CharForward:
141
o.doSelect()
142
case CharBell, CharInterrupt:
143
o.ExitCompleteMode(true)
144
next = false
145
case CharNext:
146
tmpChoise := o.candidateChoise + o.candidateColNum
147
if tmpChoise >= o.getMatrixSize() {
148
tmpChoise -= o.getMatrixSize()
149
} else if tmpChoise >= len(o.candidate) {
150
tmpChoise += o.candidateColNum
151
tmpChoise -= o.getMatrixSize()
152
}
153
o.candidateChoise = tmpChoise
154
case CharBackward:
155
o.nextCandidate(-1)
156
case CharPrev:
157
tmpChoise := o.candidateChoise - o.candidateColNum
158
if tmpChoise < 0 {
159
tmpChoise += o.getMatrixSize()
160
if tmpChoise >= len(o.candidate) {
161
tmpChoise -= o.candidateColNum
162
}
163
}
164
o.candidateChoise = tmpChoise
165
default:
166
next = false
167
o.ExitCompleteSelectMode()
168
}
169
if next {
170
o.CompleteRefresh()
171
return true
172
}
173
return false
174
}
175
176
func (o *opCompleter) getMatrixSize() int {
177
line := len(o.candidate) / o.candidateColNum
178
if len(o.candidate)%o.candidateColNum != 0 {
179
line++
180
}
181
return line * o.candidateColNum
182
}
183
184
func (o *opCompleter) OnWidthChange(newWidth int) {
185
o.width = newWidth
186
}
187
188
func (o *opCompleter) CompleteRefresh() {
189
if !o.inCompleteMode {
190
return
191
}
192
lineCnt := o.op.buf.CursorLineCount()
193
colWidth := 0
194
for _, c := range o.candidate {
195
w := runes.WidthAll(c)
196
if w > colWidth {
197
colWidth = w
198
}
199
}
200
colWidth += o.candidateOff + 1
201
same := o.op.buf.RuneSlice(-o.candidateOff)
202
203
// -1 to avoid reach the end of line
204
width := o.width - 1
205
colNum := width / colWidth
206
if colNum != 0 {
207
colWidth += (width - (colWidth * colNum)) / colNum
208
}
209
210
o.candidateColNum = colNum
211
buf := bufio.NewWriter(o.w)
212
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
213
214
colIdx := 0
215
lines := 1
216
buf.WriteString("\033[J")
217
for idx, c := range o.candidate {
218
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
219
if inSelect {
220
buf.WriteString("\033[30;47m")
221
}
222
buf.WriteString(string(same))
223
buf.WriteString(string(c))
224
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
225
226
if inSelect {
227
buf.WriteString("\033[0m")
228
}
229
230
colIdx++
231
if colIdx == colNum {
232
buf.WriteString("\n")
233
lines++
234
colIdx = 0
235
}
236
}
237
238
// move back
239
fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
240
fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
241
buf.Flush()
242
}
243
244
func (o *opCompleter) aggCandidate(candidate [][]rune) int {
245
offset := 0
246
for i := 0; i < len(candidate[0]); i++ {
247
for j := 0; j < len(candidate)-1; j++ {
248
if i > len(candidate[j]) {
249
goto aggregate
250
}
251
if candidate[j][i] != candidate[j+1][i] {
252
goto aggregate
253
}
254
}
255
offset = i
256
}
257
aggregate:
258
return offset
259
}
260
261
func (o *opCompleter) EnterCompleteSelectMode() {
262
o.inSelectMode = true
263
o.candidateChoise = -1
264
o.CompleteRefresh()
265
}
266
267
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
268
o.inCompleteMode = true
269
o.candidate = candidate
270
o.candidateOff = offset
271
o.CompleteRefresh()
272
}
273
274
func (o *opCompleter) ExitCompleteSelectMode() {
275
o.inSelectMode = false
276
o.candidate = nil
277
o.candidateChoise = -1
278
o.candidateOff = -1
279
o.candidateSource = nil
280
}
281
282
func (o *opCompleter) ExitCompleteMode(revent bool) {
283
o.inCompleteMode = false
284
o.ExitCompleteSelectMode()
285
}
286
287