Commit 30851656591293ed2e19340ab78c937855a11143

Parents: 08ef572e08665ec3a06ea6ac315f3dd6d0eac5d1

From: Robin Jarry <robin@jarry.cc>
Date: Tue Oct 17 00:14:49 2023 +0700

commands: remove command set completion api
Do not expose the completion of a command via its command set. Instead,
require a single command object to be resolved in order to execute it.
Extract the command names and the template terms completions in main.go.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Reviewed-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Moritz Poldrack <moritz@poldrack.dev>
Tested-by: Inwit <inwit@sindominio.net>

Stats

commands/commands.go +13/-49
commands/prompt.go +1/-1
main.go +28/-11

Changeset

  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
diff --git a/commands/commands.go b/commands/commands.go
index c2137a585200dacaef35ca7336077e373a156992..d76194af90df9603823a99734e2d69faa8e1b221 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"errors"
 	"reflect"
-	"sort"
 	"strings"
 	"unicode"
 
@@ -153,11 +152,6 @@
 func GetTemplateCompletion(
 	cmd string,
 ) ([]string, string, bool) {
-	args, err := splitCmd(cmd)
-	if err != nil || len(args) == 0 {
-		return nil, "", false
-	}
-
 	countLeft := strings.Count(cmd, "{{")
 	if countLeft == 0 {
 		return nil, "", false
@@ -197,48 +191,16 @@ 	return nil, "", false
 }
 
 // GetCompletions returns the completion options and the command prefix
-func (cmds *Commands) GetCompletions(
-	cmd string,
+func GetCompletions(
+	cmd Command, args *opt.Args,
 ) (options []string, prefix string) {
-	log.Tracef("completing command: %s", cmd)
-
-	// start completion
-	args, err := splitCmd(cmd)
-	if err != nil {
-		return
-	}
-
-	// nothing entered, list all commands
-	if len(args) == 0 {
-		options = cmds.Names()
-		sort.Strings(options)
-		return
-	}
-
-	// complete command name
-	spaceTerminated := cmd[len(cmd)-1] == ' '
-	if len(args) == 1 && !spaceTerminated {
-		for _, n := range cmds.Names() {
-			options = append(options, n+" ")
-		}
-		options = CompletionFromList(options, args)
-
-		return
-	}
-
-	// look for command in dictionary
-	c, ok := cmds.dict()[args[0]]
-	if !ok {
-		return
-	}
-
 	// complete options
 	var spec string
-	if provider, ok := c.(OptionsProvider); ok {
+	if provider, ok := cmd.(OptionsProvider); ok {
 		spec = provider.Options()
 	}
 
-	parser, err := newParser(cmd, spec, spaceTerminated)
+	parser, err := newParser(args.String(), spec, strings.HasSuffix(args.String(), " "))
 	if err != nil {
 		log.Debugf("completion parser failed: %v", err)
 		return
@@ -256,15 +218,15 @@ 				option += " "
 			}
 			options = append(options, option)
 		}
-		prefix = cmd
+		prefix = args.String()
 	case OPTION_ARGUMENT:
-		cmpl, ok := c.(OptionCompleter)
+		cmpl, ok := cmd.(OptionCompleter)
 		if !ok {
 			return
 		}
-		stem := cmd
+		stem := args.String()
 		if parser.arg != "" {
-			stem = strings.TrimSuffix(cmd, parser.arg)
+			stem = strings.TrimSuffix(stem, parser.arg)
 		}
 		pad := ""
 		if !strings.HasSuffix(stem, " ") {
@@ -277,14 +239,16 @@ 			options = append(options, pad+escape(option)+" ")
 		}
 		prefix = stem
 	case OPERAND:
-		stem := strings.Join(args[:parser.optind], " ")
-		for _, option := range c.Complete(args[1:]) {
+		clone := args.Clone()
+		clone.Cut(clone.Count() - parser.optind)
+		args.Shift(1)
+		for _, option := range cmd.Complete(args.Args()) {
 			if strings.Contains(option, "  ") {
 				option = escape(option)
 			}
 			options = append(options, " "+option)
 		}
-		prefix = stem
+		prefix = clone.String()
 	}
 
 	return
diff --git a/commands/prompt.go b/commands/prompt.go
index d42a659715e99ef1dcba4ba7f5a40cc369bf9874..dd259c303c2dc8a44297ff2f78f20f0c91a8723c 100644
--- a/commands/prompt.go
+++ b/commands/prompt.go
@@ -40,7 +40,7 @@ 	} else {
 		if hascommand {
 			return nil
 		}
-		cs, _ = GlobalCommands.GetCompletions(args[1])
+		cs = GlobalCommands.Names()
 	}
 	if cs == nil {
 		return nil
diff --git a/main.go b/main.go
index 43e980e9455ba596589229adce38b9c312524f49..dc7d0267abfb4ae44c2bdc0424af78366b39d3b9 100644
--- a/main.go
+++ b/main.go
@@ -125,21 +125,38 @@ 	}
 	return err
 }
 
-func getCompletions(cmd string) ([]string, string) {
-	if options, prefix, ok := commands.GetTemplateCompletion(cmd); ok {
+func getCompletions(cmdline string) ([]string, string) {
+	cmdline = strings.TrimLeft(cmdline, ":")
+
+	// complete template terms
+	if options, prefix, ok := commands.GetTemplateCompletion(cmdline); ok {
+		sort.Strings(options)
 		return options, prefix
 	}
-	var completions []string
-	var prefix string
-	for _, set := range getCommands(app.SelectedTabContent()) {
-		options, s := set.GetCompletions(cmd)
-		if s != "" {
-			prefix = s
+
+	args := opt.LexArgs(cmdline)
+	cmds := getCommands(app.SelectedTabContent())
+
+	if args.Count() < 2 && args.TrailingSpace() == "" {
+		// complete command names
+		var completions []string
+		for _, set := range cmds {
+			for _, n := range set.Names() {
+				if strings.HasPrefix(n, cmdline) {
+					completions = append(completions, n)
+				}
+			}
 		}
-		completions = append(completions, options...)
+		sort.Strings(completions)
+		return completions, ""
+	}
+
+	// complete command arguments
+	_, cmd := expandAbbreviations(args.Arg(0), cmds)
+	if cmd == nil {
+		return nil, cmdline
 	}
-	sort.Strings(completions)
-	return completions, prefix
+	return commands.GetCompletions(cmd, args)
 }
 
 // set at build time