forked from reeflective/readline
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompletions.go
360 lines (316 loc) · 10.7 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
package readline
import (
"fmt"
)
// Completion represents a completion candidate.
type Completion struct {
Value string // Value is the value of the completion as actually inserted in the line
Display string // When display is not nil, this string is used to display the completion in the menu.
Description string // A description to display next to the completion candidate.
Style string // An arbitrary string of color/text effects to use when displaying the completion.
Tag string // All completions with the same tag are grouped together and displayed under the tag heading.
// A list of runes that are automatically trimmed when a space or a non-nil character is
// inserted immediately after the completion. This is used for slash-autoremoval in path
// completions, comma-separated completions, etc.
noSpace suffixMatcher
}
// 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 rawValues
messages messages
noSpace suffixMatcher
usage string
listLong map[string]bool
noSort map[string]bool
listSep map[string]string
// 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 common prefix for all matches.
PREFIX 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 ActionValues but also accepts a style.
func CompleteStyledValues(values ...string) Completions {
if length := len(values); length%2 != 0 {
return Message("invalid amount of arguments [ActionStyledValues]: %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 Message("invalid amount of arguments [ActionValuesDescribed]: %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 ActionValues but also accepts a style.
func CompleteStyledValuesDescribed(values ...string) Completions {
if length := len(values); length%3 != 0 {
return Message("invalid amount of arguments [ActionStyledValuesDescribed]: %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}
}
// CompleteRaw directly accepts a list of prepared Completion values.
func CompleteRaw(values []Completion) Completions {
return Completions{values: rawValues(values)}
}
// Message displays a help messages in places where no completions can be generated.
func Message(msg string, args ...interface{}) Completions {
c := Completions{}
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
c.messages.Add(msg)
return c
}
// Suppress suppresses specific error messages using regular expressions.
func (c Completions) Suppress(expr ...string) Completions {
if err := c.messages.Suppress(expr...); err != nil {
return Message(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 := ActionValues("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 := ActionValues("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 ...interface{}) Completions {
return c.UsageF(func() string {
return fmt.Sprintf(usage, args...)
})
}
// Usage 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.
//
// ActionValues("yes").Style("35")
// ActionValues("no").Style("255")
func (c Completions) Style(style string) Completions {
return c.StyleF(func(s string) string {
return style
})
}
// Style sets the style using a reference
//
// ActionValues("value").StyleR(&style.Value)
// ActionValues("description").StyleR(&style.Value)
func (c Completions) StyleR(style *string) Completions {
if style != nil {
return c.Style(*style)
}
return c
}
// Style sets the style using a function
//
// ActionValues("dir/", "test.txt").StyleF(myStyleFunc)
// ActionValues("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.
//
// ActionValues("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
})
}
// Tag sets the tag using a function.
//
// ActionValues("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 Message("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 := ActionValues("A", "B", "C").Invoke(c)
// b := a.Filter([]string{"B"}) // ["A", "C"]
func (c Completions) Filter(values []string) Completions {
c.values = rawValues(c.values).Filter(values...)
return c
}
// Merge merges Completions (existing values are overwritten)
//
// a := ActionValues("A", "B").Invoke(c)
// b := ActionValues("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
}
// rawValues is a list of completion candidates.
type rawValues []Completion
// Filter filters values.
func (c rawValues) Filter(values ...string) rawValues {
toremove := make(map[string]bool)
for _, v := range values {
toremove[v] = true
}
filtered := make([]Completion, 0)
for _, rawValue := range c {
if _, ok := toremove[rawValue.Value]; !ok {
filtered = append(filtered, rawValue)
}
}
return filtered
}
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
}
}
}
func (c rawValues) eachTag(f func(tag string, values rawValues)) {
tags := make([]string, 0)
tagGroups := make(map[string]rawValues)
for _, val := range c {
if _, exists := tagGroups[val.Tag]; !exists {
tagGroups[val.Tag] = make(rawValues, 0)
tags = append(tags, val.Tag)
}
tagGroups[val.Tag] = append(tagGroups[val.Tag], val)
}
for _, tag := range tags {
f(tag, tagGroups[tag])
}
}