-
Notifications
You must be signed in to change notification settings - Fork 12
/
completions.go
453 lines (369 loc) · 12.4 KB
/
completions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
package readline
import (
"fmt"
"github.com/reeflective/readline/internal/completion"
)
// Completion represents a completion candidate.
type Completion = completion.Candidate
// Completions holds all completions candidates and their associated data,
// including usage strings, messages, and suffix matchers for autoremoval.
// Some of those additional settings will apply to all contained candidates,
// except when these candidates have their own corresponding settings.
type Completions struct {
values completion.RawValues
messages completion.Messages
noSpace completion.SuffixMatcher
usage string
listLong map[string]bool
noSort map[string]bool
listSep map[string]string
pad map[string]bool
escapes map[string]bool
// Initially this will be set to the part of the current word
// from the beginning of the word up to the position of the cursor.
// It may be altered to give a prefix for all matches.
PREFIX string
// Initially this will be set to the part of the current word,
// starting from the cursor position up to the end of the word.
// It may be altered so that inserted completions don't overwrite
// entirely any suffix when completing in the middle of a word.
SUFFIX string
}
// CompleteValues completes arbitrary keywords (values).
func CompleteValues(values ...string) Completions {
vals := make([]Completion, 0, len(values))
for _, val := range values {
vals = append(vals, Completion{Value: val, Display: val, Description: ""})
}
return Completions{values: vals}
}
// CompleteStyledValues is like CompleteValues but also accepts a style.
func CompleteStyledValues(values ...string) Completions {
if length := len(values); length%2 != 0 {
return CompleteMessage("invalid amount of arguments [CompleteStyledValues]: %v", length)
}
vals := make([]Completion, 0, len(values)/2)
for i := 0; i < len(values); i += 2 {
vals = append(vals, Completion{Value: values[i], Display: values[i], Description: "", Style: values[i+1]})
}
return Completions{values: vals}
}
// CompleteValuesDescribed completes arbitrary key (values) with an additional description (value, description pairs).
func CompleteValuesDescribed(values ...string) Completions {
if length := len(values); length%2 != 0 {
return CompleteMessage("invalid amount of arguments [CompleteValuesDescribed]: %v", length)
}
vals := make([]Completion, 0, len(values)/2)
for i := 0; i < len(values); i += 2 {
vals = append(vals, Completion{Value: values[i], Display: values[i], Description: values[i+1]})
}
return Completions{values: vals}
}
// CompleteStyledValuesDescribed is like CompleteValues but also accepts a style.
func CompleteStyledValuesDescribed(values ...string) Completions {
if length := len(values); length%3 != 0 {
return CompleteMessage("invalid amount of arguments [CompleteStyledValuesDescribed]: %v", length)
}
vals := make([]Completion, 0, len(values)/3)
for i := 0; i < len(values); i += 3 {
vals = append(vals, Completion{Value: values[i], Display: values[i], Description: values[i+1], Style: values[i+2]})
}
return Completions{values: vals}
}
// CompleteMessage ads a help message to display along with
// or in places where no completions can be generated.
func CompleteMessage(msg string, args ...any) Completions {
comps := Completions{}
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
comps.messages.Add(msg)
return comps
}
// CompleteRaw directly accepts a list of prepared Completion values.
func CompleteRaw(values []Completion) Completions {
return Completions{values: completion.RawValues(values)}
}
// Message displays a help messages in places where no completions can be generated.
func Message(msg string, args ...any) Completions {
comps := Completions{}
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
comps.messages.Add(msg)
return comps
}
// Suppress suppresses specific error messages using regular expressions.
func (c Completions) Suppress(expr ...string) Completions {
if err := c.messages.Suppress(expr...); err != nil {
return CompleteMessage(err.Error())
}
return c
}
// NoSpace disables space suffix for given characters (or all if none are given).
// These suffixes will be used for all completions that have not specified their
// own suffix-matching patterns.
// This is used for slash-autoremoval in path completions, comma-separated completions, etc.
func (c Completions) NoSpace(suffixes ...rune) Completions {
if len(suffixes) == 0 {
c.noSpace.Add('*')
}
c.noSpace.Add(suffixes...)
return c
}
// Prefix adds a prefix to values (only the ones inserted, not the display values)
//
// a := CompleteValues("melon", "drop", "fall").Invoke(c)
// b := a.Prefix("water") // ["watermelon", "waterdrop", "waterfall"] but display still ["melon", "drop", "fall"]
func (c Completions) Prefix(prefix string) Completions {
for index, val := range c.values {
c.values[index].Value = prefix + val.Value
}
return c
}
// Suffix adds a suffx to values (only the ones inserted, not the display values)
//
// a := CompleteValues("apple", "melon", "orange").Invoke(c)
// b := a.Suffix("juice") // ["applejuice", "melonjuice", "orangejuice"] but display still ["apple", "melon", "orange"]
func (c Completions) Suffix(suffix string) Completions {
for index, val := range c.values {
c.values[index].Value = val.Value + suffix
}
return c
}
// Usage sets the usage.
func (c Completions) Usage(usage string, args ...any) Completions {
return c.UsageF(func() string {
return fmt.Sprintf(usage, args...)
})
}
// UsageF sets the usage using a function.
func (c Completions) UsageF(f func() string) Completions {
if usage := f(); usage != "" {
c.usage = usage
}
return c
}
// Style sets the style, accepting cterm color codes, eg. 255, 30, etc.
//
// CompleteValues("yes").Style("35")
// CompleteValues("no").Style("255")
func (c Completions) Style(style string) Completions {
return c.StyleF(func(s string) string {
return style
})
}
// StyleR sets the style using a reference
//
// CompleteValues("value").StyleR(&style.Value)
// CompleteValues("description").StyleR(&style.Value)
func (c Completions) StyleR(style *string) Completions {
if style != nil {
return c.Style(*style)
}
return c
}
// StyleF sets the style using a function
//
// CompleteValues("dir/", "test.txt").StyleF(myStyleFunc)
// CompleteValues("true", "false").StyleF(styleForKeyword)
func (c Completions) StyleF(f func(s string) string) Completions {
for index, v := range c.values {
c.values[index].Style = f(v.Value)
}
return c
}
// Tag sets the tag.
//
// CompleteValues("192.168.1.1", "127.0.0.1").Tag("interfaces").
func (c Completions) Tag(tag string) Completions {
return c.TagF(func(value string) string {
return tag
})
}
// TagF sets the tag using a function.
//
// CompleteValues("192.168.1.1", "127.0.0.1").TagF(func(value string) string {
// return "interfaces"
// })
func (c Completions) TagF(f func(value string) string) Completions {
for index, v := range c.values {
c.values[index].Tag = f(v.Value)
}
return c
}
// DisplayList forces the completions to be list below each other as a list.
// A series of tags can be passed to restrict this to these tags. If empty,
// will be applied to all completions.
func (c Completions) DisplayList(tags ...string) Completions {
if c.listLong == nil {
c.listLong = make(map[string]bool)
}
if len(tags) == 0 {
c.listLong["*"] = true
}
for _, tag := range tags {
c.listLong[tag] = true
}
return c
}
// ListSeparator accepts a custom separator to use between the candidates and their descriptions.
// If more than one separator is given, the list is considered to be a map of tag:separators, in
// which case it will fail if the list has an odd number of values.
//
// If one only one value is given, will apply to all completions (and their tags if any).
// If no value is given, no modifications will be made.
func (c Completions) ListSeparator(seps ...string) Completions {
if c.listSep == nil {
c.listSep = make(map[string]string)
}
if length := len(seps); len(seps) > 1 && length%2 != 0 {
return CompleteMessage("invalid amount of arguments (ListSeparator): %v", length)
}
if len(seps) == 1 {
if len(c.listSep) == 0 {
c.listSep["*"] = seps[0]
} else {
for tag := range c.listSep {
c.listSep[tag] = seps[0]
}
}
} else {
for i := 0; i < len(seps); i += 2 {
c.listSep[seps[i]] = seps[i+1]
}
}
return c
}
// NoSort forces the completions not to sort the completions in alphabetical order.
// A series of tags can be passed to restrict this to these tags. If empty, will be
// applied to all completions.
func (c Completions) NoSort(tags ...string) Completions {
if c.noSort == nil {
c.noSort = make(map[string]bool)
}
if len(tags) == 0 {
c.noSort["*"] = true
}
for _, tag := range tags {
c.noSort[tag] = true
}
return c
}
// Filter filters given values (this should be done before any call
// to Prefix/Suffix as those alter the values being filtered)
//
// a := CompleteValues("A", "B", "C").Invoke(c)
// b := a.Filter([]string{"B"}) // ["A", "C"]
func (c Completions) Filter(values []string) Completions {
c.values = c.values.Filter(values...)
return c
}
// JustifyDescriptions accepts a list of tags for which descriptions (if any), will be left justified.
// If no arguments are given, description justification (padding) will apply to all tags.
func (c Completions) JustifyDescriptions(tags ...string) Completions {
if c.pad == nil {
c.pad = make(map[string]bool)
}
if len(tags) == 0 {
c.pad["*"] = true
}
for _, tag := range tags {
c.pad[tag] = true
}
return c
}
// PreserveEscapes forces the completion engine to keep all escaped characters in
// the inserted completion (c.Value of the Completion type). By default, those are
// stripped out and only kept in the completion.Display. If no arguments are given,
// escape sequence preservation will apply to all tags.
//
// This has very few use cases: one of them might be when you want to read a string
// from the readline shell that might include color sequences to be preserved.
// In such cases, this function gives a double advantage: the resulting completion
// is still "color-displayed" in the input line, and returned to the readline with
// them. A classic example is where you want to read a prompt string configuration.
//
// Note that this option might have various undefined behaviors when it comes to
// completion prefix matching, insertion, removal and related things.
func (c Completions) PreserveEscapes(tags ...string) Completions {
if c.escapes == nil {
c.escapes = make(map[string]bool)
}
if len(tags) == 0 {
c.escapes["*"] = true
}
for _, tag := range tags {
c.escapes[tag] = true
}
return c
}
// Merge merges Completions (existing values are overwritten)
//
// a := CompleteValues("A", "B").Invoke(c)
// b := CompleteValues("B", "C").Invoke(c)
// c := a.Merge(b) // ["A", "B", "C"]
func (c Completions) Merge(others ...Completions) Completions {
uniqueRawValues := make(map[string]Completion)
for _, other := range append([]Completions{c}, others...) {
for _, c := range other.values {
uniqueRawValues[c.Value] = c
}
}
for _, other := range others {
c.merge(other)
}
rawValues := make([]Completion, 0, len(uniqueRawValues))
for _, c := range uniqueRawValues {
rawValues = append(rawValues, c)
}
c.values = rawValues
return c
}
// EachValue runs a function on each value, overwriting with the returned one.
func (c *Completions) EachValue(tagF func(comp Completion) Completion) {
for index, v := range c.values {
c.values[index] = tagF(v)
}
}
func (c *Completions) merge(other Completions) {
if other.usage != "" {
c.usage = other.usage
}
c.noSpace.Merge(other.noSpace)
c.messages.Merge(other.messages)
for tag := range other.listLong {
if _, found := c.listLong[tag]; !found {
c.listLong[tag] = true
}
}
for tag := range other.noSort {
if _, found := c.noSort[tag]; !found {
c.noSort[tag] = true
}
}
for tag := range other.listSep {
if _, found := c.listSep[tag]; !found {
c.listSep[tag] = other.listSep[tag]
}
}
for tag := range other.pad {
if _, found := c.pad[tag]; !found {
c.pad[tag] = other.pad[tag]
}
}
}
func (c *Completions) convert() completion.Values {
comps := completion.AddRaw(c.values)
comps.Messages = c.messages
comps.NoSpace = c.noSpace
comps.Usage = c.usage
comps.ListLong = c.listLong
comps.NoSort = c.noSort
comps.ListSep = c.listSep
comps.Pad = c.pad
comps.Escapes = c.escapes
comps.PREFIX = c.PREFIX
comps.SUFFIX = c.SUFFIX
return comps
}