summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoe Lim <50560759+joelim-work@users.noreply.github.com>2024-02-12 10:19:17 +1100
committerGitHub <noreply@github.com>2024-02-12 10:19:17 +1100
commitab54329570b349a57ca2bb832b7ebd63174e3a02 (patch)
treebbc77a857d248491476f0bfb095c027fa4b22932
parentac32468629a1ccd4f4456bafbc4ff926f4a7a75c (diff)
Dynamically generate list of completions (#1595)
* Dynamically generate list of completions * Update contribution guide * Add unit tests
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--complete.go174
-rw-r--r--complete_test.go38
3 files changed, 75 insertions, 138 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e720b8f..dac133f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,7 +27,6 @@ Adding a new option usually requires the following steps:
- Add default option value to `init` function in `opts.go`
- Add option evaluation logic to `setExpr.eval` in `eval.go`
- Implement the option somewhere in the code
-- Add option name to `gOptWords` in `complete.go` for tab completion
- Add option name and its default value to `Quick Reference` and `Options` sections in `doc.md`
- Run `gen/doc-with-docker.sh` to update the documentation
- Commit your changes and send a pull request
diff --git a/complete.go b/complete.go
index e0055da..cd962a5 100644
--- a/complete.go
+++ b/complete.go
@@ -4,6 +4,7 @@ import (
"log"
"os"
"path/filepath"
+ "reflect"
"sort"
"strings"
)
@@ -109,146 +110,45 @@ var (
"cmd-lowercase-word",
}
- gOptWords = []string{
- "anchorfind",
- "noanchorfind",
- "anchorfind!",
- "autoquit",
- "noautoquit",
- "autoquit!",
- "borderfmt",
- "copyfmt",
- "cursoractivefmt",
- "cursorparentfmt",
- "cursorpreviewfmt",
- "cutfmt",
- "hidecursorinactive",
- "nohidecursorinactive",
- "hidecursorinactive!",
- "dircache",
- "nodircache",
- "dircache!",
- "dircounts",
- "nodircounts",
- "dircounts!",
- "dirfirst",
- "nodirfirst",
- "dirfirst!",
- "dironly",
- "nodironly",
- "dironly!",
- "dirpreviews",
- "nodirpreviews",
- "dirpreviews!",
- "drawbox",
- "nodrawbox",
- "drawbox!",
- "dupfilefmt",
- "globsearch",
- "noglobsearch",
- "globsearch!",
- "hidden",
- "nohidden",
- "hidden!",
- "history",
- "nohistory",
- "history!",
- "icons",
- "noicons",
- "icons!",
- "ignorecase",
- "noignorecase",
- "ignorecase!",
- "ignoredia",
- "noignoredia",
- "ignoredia!",
- "incsearch",
- "noincsearch",
- "incsearch!",
- "incfilter",
- "noincfilter",
- "incfilter!",
- "mouse",
- "nomouse",
- "mouse!",
- "number",
- "nonumber",
- "number!",
- "preview",
- "nopreview",
- "preview!",
- "relativenumber",
- "norelativenumber",
- "relativenumber!",
- "reverse",
- "noreverse",
- "reverse!",
- "ruler",
- "rulerfmt",
- "preserve",
- "selectfmt",
- "sixel",
- "nosixel",
- "sixel!",
- "smartcase",
- "nosmartcase",
- "smartcase!",
- "smartdia",
- "nosmartdia",
- "smartdia!",
- "waitmsg",
- "wrapscan",
- "nowrapscan",
- "wrapscan!",
- "wrapscroll",
- "nowrapscroll",
- "wrapscroll!",
- "findlen",
- "period",
- "scrolloff",
- "tabstop",
- "errorfmt",
- "filesep",
- "hiddenfiles",
- "ifs",
- "info",
- "numberfmt",
- "previewer",
- "cleaner",
- "promptfmt",
- "ratios",
- "selmode",
- "shell",
- "shellflag",
- "shellopts",
- "sortby",
- "statfmt",
- "timefmt",
- "tempmarks",
- "tagfmt",
- "infotimefmtnew",
- "infotimefmtold",
- "truncatechar",
- "truncatepct",
+ gOptWords = getOptWords(gOpts)
+ gLocalOptWords = getLocalOptWords(gLocalOpts)
+)
+
+func getOptWords(opts any) (optWords []string) {
+ t := reflect.TypeOf(opts)
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ switch field.Type.Kind() {
+ case reflect.Map:
+ continue
+ case reflect.Bool:
+ name := field.Name
+ optWords = append(optWords, name, "no"+name, name+"!")
+ default:
+ optWords = append(optWords, field.Name)
+ }
}
+ sort.Strings(optWords)
+ return
+}
- gLocalOptWords = []string{
- "dirfirst",
- "nodirfirst",
- "dirfirst!",
- "dironly",
- "nodironly",
- "dironly!",
- "hidden",
- "nohidden",
- "hidden!",
- "info",
- "reverse",
- "noreverse",
- "reverse!",
- "sortby",
+func getLocalOptWords(localOpts any) (localOptWords []string) {
+ t := reflect.TypeOf(localOpts)
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ name := strings.TrimSuffix(field.Name, "s")
+ if field.Type.Kind() != reflect.Map {
+ continue
+ }
+ if field.Type.Elem().Kind() == reflect.Bool {
+ localOptWords = append(localOptWords, name, "no"+name, name+"!")
+ } else {
+ localOptWords = append(localOptWords, name)
+ }
}
-)
+ sort.Strings(localOptWords)
+ return
+}
func matchLongest(s1, s2 []rune) []rune {
i := 0
diff --git a/complete_test.go b/complete_test.go
index 36994dd..51896b9 100644
--- a/complete_test.go
+++ b/complete_test.go
@@ -54,3 +54,41 @@ func TestMatchWord(t *testing.T) {
}
}
}
+
+func TestGetOptWords(t *testing.T) {
+ tests := []struct {
+ opts any
+ exp []string
+ }{
+ {struct{ feature bool }{}, []string{"feature", "feature!", "nofeature"}},
+ {struct{ feature int }{}, []string{"feature"}},
+ {struct{ feature string }{}, []string{"feature"}},
+ {struct{ feature []string }{}, []string{"feature"}},
+ }
+
+ for _, test := range tests {
+ result := getOptWords(test.opts)
+ if !reflect.DeepEqual(result, test.exp) {
+ t.Errorf("at input '%#v' expected '%s' but got '%s'", test.opts, test.exp, result)
+ }
+ }
+}
+
+func TestGetLocalOptWords(t *testing.T) {
+ tests := []struct {
+ localOpts any
+ exp []string
+ }{
+ {struct{ features map[string]bool }{}, []string{"feature", "feature!", "nofeature"}},
+ {struct{ features map[string]int }{}, []string{"feature"}},
+ {struct{ features map[string]string }{}, []string{"feature"}},
+ {struct{ features map[string][]string }{}, []string{"feature"}},
+ }
+
+ for _, test := range tests {
+ result := getLocalOptWords(test.localOpts)
+ if !reflect.DeepEqual(result, test.exp) {
+ t.Errorf("at input '%#v' expected '%s' but got '%s'", test.localOpts, test.exp, result)
+ }
+ }
+}