Skip to content

Commit ab54329

Browse files
authored
Dynamically generate list of completions (#1595)
* Dynamically generate list of completions * Update contribution guide * Add unit tests
1 parent ac32468 commit ab54329

File tree

3 files changed

+75
-138
lines changed

3 files changed

+75
-138
lines changed

CONTRIBUTING.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ Adding a new option usually requires the following steps:
2727
- Add default option value to `init` function in `opts.go`
2828
- Add option evaluation logic to `setExpr.eval` in `eval.go`
2929
- Implement the option somewhere in the code
30-
- Add option name to `gOptWords` in `complete.go` for tab completion
3130
- Add option name and its default value to `Quick Reference` and `Options` sections in `doc.md`
3231
- Run `gen/doc-with-docker.sh` to update the documentation
3332
- Commit your changes and send a pull request

complete.go

Lines changed: 37 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"log"
55
"os"
66
"path/filepath"
7+
"reflect"
78
"sort"
89
"strings"
910
)
@@ -109,146 +110,45 @@ var (
109110
"cmd-lowercase-word",
110111
}
111112

112-
gOptWords = []string{
113-
"anchorfind",
114-
"noanchorfind",
115-
"anchorfind!",
116-
"autoquit",
117-
"noautoquit",
118-
"autoquit!",
119-
"borderfmt",
120-
"copyfmt",
121-
"cursoractivefmt",
122-
"cursorparentfmt",
123-
"cursorpreviewfmt",
124-
"cutfmt",
125-
"hidecursorinactive",
126-
"nohidecursorinactive",
127-
"hidecursorinactive!",
128-
"dircache",
129-
"nodircache",
130-
"dircache!",
131-
"dircounts",
132-
"nodircounts",
133-
"dircounts!",
134-
"dirfirst",
135-
"nodirfirst",
136-
"dirfirst!",
137-
"dironly",
138-
"nodironly",
139-
"dironly!",
140-
"dirpreviews",
141-
"nodirpreviews",
142-
"dirpreviews!",
143-
"drawbox",
144-
"nodrawbox",
145-
"drawbox!",
146-
"dupfilefmt",
147-
"globsearch",
148-
"noglobsearch",
149-
"globsearch!",
150-
"hidden",
151-
"nohidden",
152-
"hidden!",
153-
"history",
154-
"nohistory",
155-
"history!",
156-
"icons",
157-
"noicons",
158-
"icons!",
159-
"ignorecase",
160-
"noignorecase",
161-
"ignorecase!",
162-
"ignoredia",
163-
"noignoredia",
164-
"ignoredia!",
165-
"incsearch",
166-
"noincsearch",
167-
"incsearch!",
168-
"incfilter",
169-
"noincfilter",
170-
"incfilter!",
171-
"mouse",
172-
"nomouse",
173-
"mouse!",
174-
"number",
175-
"nonumber",
176-
"number!",
177-
"preview",
178-
"nopreview",
179-
"preview!",
180-
"relativenumber",
181-
"norelativenumber",
182-
"relativenumber!",
183-
"reverse",
184-
"noreverse",
185-
"reverse!",
186-
"ruler",
187-
"rulerfmt",
188-
"preserve",
189-
"selectfmt",
190-
"sixel",
191-
"nosixel",
192-
"sixel!",
193-
"smartcase",
194-
"nosmartcase",
195-
"smartcase!",
196-
"smartdia",
197-
"nosmartdia",
198-
"smartdia!",
199-
"waitmsg",
200-
"wrapscan",
201-
"nowrapscan",
202-
"wrapscan!",
203-
"wrapscroll",
204-
"nowrapscroll",
205-
"wrapscroll!",
206-
"findlen",
207-
"period",
208-
"scrolloff",
209-
"tabstop",
210-
"errorfmt",
211-
"filesep",
212-
"hiddenfiles",
213-
"ifs",
214-
"info",
215-
"numberfmt",
216-
"previewer",
217-
"cleaner",
218-
"promptfmt",
219-
"ratios",
220-
"selmode",
221-
"shell",
222-
"shellflag",
223-
"shellopts",
224-
"sortby",
225-
"statfmt",
226-
"timefmt",
227-
"tempmarks",
228-
"tagfmt",
229-
"infotimefmtnew",
230-
"infotimefmtold",
231-
"truncatechar",
232-
"truncatepct",
113+
gOptWords = getOptWords(gOpts)
114+
gLocalOptWords = getLocalOptWords(gLocalOpts)
115+
)
116+
117+
func getOptWords(opts any) (optWords []string) {
118+
t := reflect.TypeOf(opts)
119+
for i := 0; i < t.NumField(); i++ {
120+
field := t.Field(i)
121+
switch field.Type.Kind() {
122+
case reflect.Map:
123+
continue
124+
case reflect.Bool:
125+
name := field.Name
126+
optWords = append(optWords, name, "no"+name, name+"!")
127+
default:
128+
optWords = append(optWords, field.Name)
129+
}
233130
}
131+
sort.Strings(optWords)
132+
return
133+
}
234134

235-
gLocalOptWords = []string{
236-
"dirfirst",
237-
"nodirfirst",
238-
"dirfirst!",
239-
"dironly",
240-
"nodironly",
241-
"dironly!",
242-
"hidden",
243-
"nohidden",
244-
"hidden!",
245-
"info",
246-
"reverse",
247-
"noreverse",
248-
"reverse!",
249-
"sortby",
135+
func getLocalOptWords(localOpts any) (localOptWords []string) {
136+
t := reflect.TypeOf(localOpts)
137+
for i := 0; i < t.NumField(); i++ {
138+
field := t.Field(i)
139+
name := strings.TrimSuffix(field.Name, "s")
140+
if field.Type.Kind() != reflect.Map {
141+
continue
142+
}
143+
if field.Type.Elem().Kind() == reflect.Bool {
144+
localOptWords = append(localOptWords, name, "no"+name, name+"!")
145+
} else {
146+
localOptWords = append(localOptWords, name)
147+
}
250148
}
251-
)
149+
sort.Strings(localOptWords)
150+
return
151+
}
252152

253153
func matchLongest(s1, s2 []rune) []rune {
254154
i := 0

complete_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,41 @@ func TestMatchWord(t *testing.T) {
5454
}
5555
}
5656
}
57+
58+
func TestGetOptWords(t *testing.T) {
59+
tests := []struct {
60+
opts any
61+
exp []string
62+
}{
63+
{struct{ feature bool }{}, []string{"feature", "feature!", "nofeature"}},
64+
{struct{ feature int }{}, []string{"feature"}},
65+
{struct{ feature string }{}, []string{"feature"}},
66+
{struct{ feature []string }{}, []string{"feature"}},
67+
}
68+
69+
for _, test := range tests {
70+
result := getOptWords(test.opts)
71+
if !reflect.DeepEqual(result, test.exp) {
72+
t.Errorf("at input '%#v' expected '%s' but got '%s'", test.opts, test.exp, result)
73+
}
74+
}
75+
}
76+
77+
func TestGetLocalOptWords(t *testing.T) {
78+
tests := []struct {
79+
localOpts any
80+
exp []string
81+
}{
82+
{struct{ features map[string]bool }{}, []string{"feature", "feature!", "nofeature"}},
83+
{struct{ features map[string]int }{}, []string{"feature"}},
84+
{struct{ features map[string]string }{}, []string{"feature"}},
85+
{struct{ features map[string][]string }{}, []string{"feature"}},
86+
}
87+
88+
for _, test := range tests {
89+
result := getLocalOptWords(test.localOpts)
90+
if !reflect.DeepEqual(result, test.exp) {
91+
t.Errorf("at input '%#v' expected '%s' but got '%s'", test.localOpts, test.exp, result)
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)