Skip to content

Commit aa6a8d3

Browse files
committed
review feedback p1
1 parent cff5dbb commit aa6a8d3

File tree

6 files changed

+62
-69
lines changed

6 files changed

+62
-69
lines changed

command.go

+39-47
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type Command struct {
5959
Middleware MiddlewareFunc
6060
Handler HandlerFunc
6161
HelpHandler HandlerFunc
62-
// CompletionHandler is called when the command is run is completion
62+
// CompletionHandler is called when the command is run in completion
6363
// mode. If nil, only the default completion handler is used.
6464
//
6565
// Flag and option parsing is best-effort in this mode, so even if an Option
@@ -187,7 +187,6 @@ func (c *Command) Invoke(args ...string) *Invocation {
187187
return &Invocation{
188188
Command: c,
189189
Args: args,
190-
AllArgs: args,
191190
Stdout: io.Discard,
192191
Stderr: io.Discard,
193192
Stdin: strings.NewReader(""),
@@ -204,9 +203,6 @@ type Invocation struct {
204203
// Args is reduced into the remaining arguments after parsing flags
205204
// during Run.
206205
Args []string
207-
// AllArgs is the original arguments passed to the command, including flags.
208-
// When invoked `WithOS`, this includes argv[0], otherwise it is the same as Args.
209-
AllArgs []string
210206
// CurWord is the word the terminal cursor is currently in
211207
CurWord string
212208

@@ -233,7 +229,6 @@ func (inv *Invocation) WithOS() *Invocation {
233229
i.Stdout = os.Stdout
234230
i.Stderr = os.Stderr
235231
i.Stdin = os.Stdin
236-
i.AllArgs = os.Args
237232
i.Args = os.Args[1:]
238233
i.Environ = ParseEnviron(os.Environ(), "")
239234
i.Net = osNet{}
@@ -302,13 +297,13 @@ func copyFlagSetWithout(fs *pflag.FlagSet, without string) *pflag.FlagSet {
302297
return fs2
303298
}
304299

305-
func (inv *Invocation) GetCurWords() (prev string, cur string) {
306-
if len(inv.AllArgs) == 1 {
307-
cur = inv.AllArgs[0]
300+
func (inv *Invocation) curWords() (prev string, cur string) {
301+
if len(inv.Args) == 1 {
302+
cur = inv.Args[0]
308303
prev = ""
309304
} else {
310-
cur = inv.AllArgs[len(inv.AllArgs)-1]
311-
prev = inv.AllArgs[len(inv.AllArgs)-2]
305+
cur = inv.Args[len(inv.Args)-1]
306+
prev = inv.Args[len(inv.Args)-2]
312307
}
313308
return
314309
}
@@ -409,7 +404,39 @@ func (inv *Invocation) run(state *runState) error {
409404
}
410405
}
411406

412-
ignoreFlagParseErrors := inv.Command.RawArgs || inv.IsCompletionMode()
407+
// Outputted completions are not filtered based on the word under the cursor, as every shell we support does this already.
408+
// We only look at the current word to figure out handler to run, or what directory to inspect.
409+
if inv.IsCompletionMode() {
410+
prev, cur := inv.curWords()
411+
inv.CurWord = cur
412+
// If the current word is a flag set using `=`, use it's handler
413+
if strings.HasPrefix(cur, "--") && strings.Contains(cur, "=") {
414+
if inv.equalsFlagHandler(cur) {
415+
return nil
416+
}
417+
}
418+
// If the previous word is a flag, then we're writing it's value
419+
// and we should check it's handler
420+
if strings.HasPrefix(prev, "--") {
421+
if inv.flagHandler(prev) {
422+
return nil
423+
}
424+
}
425+
// If the current word is the command, auto-complete it so the shell moves the cursor
426+
if inv.Command.Name() == inv.CurWord {
427+
fmt.Fprintf(inv.Stdout, "%s\n", inv.Command.Name())
428+
return nil
429+
}
430+
if inv.Command.CompletionHandler == nil {
431+
inv.Command.CompletionHandler = DefaultCompletionHandler
432+
}
433+
for _, e := range inv.Command.CompletionHandler(inv) {
434+
fmt.Fprintf(inv.Stdout, "%s\n", e)
435+
}
436+
return nil
437+
}
438+
439+
ignoreFlagParseErrors := inv.Command.RawArgs
413440

414441
// Flag parse errors are irrelevant for raw args commands.
415442
if !ignoreFlagParseErrors && state.flagParseErr != nil && !errors.Is(state.flagParseErr, pflag.ErrHelp) {
@@ -464,39 +491,6 @@ func (inv *Invocation) run(state *runState) error {
464491
defer cancel()
465492
inv = inv.WithContext(ctx)
466493

467-
// Outputted completions are not filtered based on the word under the cursor, as every shell we support does this already.
468-
// We only look at the current word to figure out handler to run, or what directory to inspect.
469-
if inv.IsCompletionMode() {
470-
prev, cur := inv.GetCurWords()
471-
inv.CurWord = cur
472-
// If the current word is a flag set using `=`, use it's handler
473-
if strings.HasPrefix(cur, "--") && strings.Contains(cur, "=") {
474-
if inv.equalsFlagHandler(cur) {
475-
return nil
476-
}
477-
}
478-
// If the previous word is a flag, then we're writing it's value
479-
// and we should check it's handler
480-
if strings.HasPrefix(prev, "--") {
481-
if inv.flagHandler(prev) {
482-
return nil
483-
}
484-
}
485-
if inv.Command.Name() == inv.CurWord {
486-
fmt.Fprintf(inv.Stdout, "%s\n", inv.Command.Name())
487-
return nil
488-
}
489-
if inv.Command.CompletionHandler != nil {
490-
for _, e := range inv.Command.CompletionHandler(inv) {
491-
fmt.Fprintf(inv.Stdout, "%s\n", e)
492-
}
493-
}
494-
for _, e := range DefaultCompletionHandler(inv) {
495-
fmt.Fprintf(inv.Stdout, "%s\n", e)
496-
}
497-
return nil
498-
}
499-
500494
if inv.Command.Handler == nil || errors.Is(state.flagParseErr, pflag.ErrHelp) {
501495
if inv.Command.HelpHandler == nil {
502496
return defaultHelpFn()(inv)
@@ -743,5 +737,3 @@ func RequireRangeArgs(start, end int) MiddlewareFunc {
743737
type HandlerFunc func(i *Invocation) error
744738

745739
type CompletionHandlerFunc func(i *Invocation) []string
746-
747-
var NopHandler HandlerFunc = func(i *Invocation) error { return nil }

command_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func fakeIO(i *serpent.Invocation) *ioBufs {
3131
return &b
3232
}
3333

34-
func SampleCommand(t *testing.T) *serpent.Command {
34+
func sampleCommand(t *testing.T) *serpent.Command {
3535
t.Helper()
3636
var (
3737
verbose bool
@@ -169,7 +169,7 @@ func SampleCommand(t *testing.T) *serpent.Command {
169169
func TestCommand(t *testing.T) {
170170
t.Parallel()
171171

172-
cmd := func() *serpent.Command { return SampleCommand(t) }
172+
cmd := func() *serpent.Command { return sampleCommand(t) }
173173

174174
t.Run("SimpleOK", func(t *testing.T) {
175175
t.Parallel()

completion/all.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ const (
2020
)
2121

2222
var shellCompletionByName = map[string]func(io.Writer, string) error{
23-
BashShell: GenerateCompletion(bashCompletionTemplate),
24-
FishShell: GenerateCompletion(fishCompletionTemplate),
25-
ZShell: GenerateCompletion(zshCompletionTemplate),
26-
Powershell: GenerateCompletion(pshCompletionTemplate),
23+
BashShell: generateCompletion(bashCompletionTemplate),
24+
FishShell: generateCompletion(fishCompletionTemplate),
25+
ZShell: generateCompletion(zshCompletionTemplate),
26+
Powershell: generateCompletion(pshCompletionTemplate),
2727
}
2828

2929
func ShellOptions(choice *string) *serpent.Enum {
3030
return serpent.EnumOf(choice, BashShell, FishShell, ZShell, Powershell)
3131
}
3232

33-
func GetCompletion(writer io.Writer, shell string, cmdName string) error {
33+
func WriteCompletion(writer io.Writer, shell string, cmdName string) error {
3434
fn, ok := shellCompletionByName[shell]
3535
if !ok {
3636
return fmt.Errorf("unknown shell %q", shell)
@@ -39,7 +39,7 @@ func GetCompletion(writer io.Writer, shell string, cmdName string) error {
3939
return nil
4040
}
4141

42-
func GetUserShell() (string, error) {
42+
func DetectUserShell() (string, error) {
4343
// Attempt to get the SHELL environment variable first
4444
if shell := os.Getenv("SHELL"); shell != "" {
4545
return filepath.Base(shell), nil
@@ -70,7 +70,7 @@ func GetUserShell() (string, error) {
7070
return "", fmt.Errorf("default shell not found")
7171
}
7272

73-
func GenerateCompletion(
73+
func generateCompletion(
7474
scriptTemplate string,
7575
) func(io.Writer, string) error {
7676
return func(w io.Writer, rootCmdName string) error {

completion/handlers.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@ import (
99
"github.com/coder/serpent"
1010
)
1111

12-
// FileHandler returns a handler that completes files, using the
12+
// FileHandler returns a handler that completes file names, using the
1313
// given filter func, which may be nil.
1414
func FileHandler(filter func(info os.FileInfo) bool) serpent.CompletionHandlerFunc {
1515
return func(inv *serpent.Invocation) []string {
16-
return ListFiles(inv.CurWord, filter)
16+
return listFiles(inv.CurWord, filter)
1717
}
1818
}
1919

20+
// FileListHandler returns a handler that completes a list of comma-separated,
21+
// file names, using the given filter func, which may be nil.
2022
func FileListHandler(filter func(info os.FileInfo) bool) serpent.CompletionHandlerFunc {
2123
return func(inv *serpent.Invocation) []string {
2224
curWord := strings.TrimLeft(inv.CurWord, `"`)
2325
if curWord == "" {
24-
return ListFiles("", filter)
26+
return listFiles("", filter)
2527
}
2628
parts := strings.Split(curWord, ",")
27-
out := ListFiles(parts[len(parts)-1], filter)
28-
// prepend := strings.Join(parts[:len(parts)-1], ",")
29+
out := listFiles(parts[len(parts)-1], filter)
2930
for i, s := range out {
3031
parts[len(parts)-1] = s
3132
out[i] = strings.Join(parts, ",")
@@ -34,7 +35,7 @@ func FileListHandler(filter func(info os.FileInfo) bool) serpent.CompletionHandl
3435
}
3536
}
3637

37-
func ListFiles(word string, filter func(info os.FileInfo) bool) []string {
38+
func listFiles(word string, filter func(info os.FileInfo) bool) []string {
3839
out := make([]string, 0, 32)
3940

4041
dir, _ := filepath.Split(word)

completion_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func TestCompletion(t *testing.T) {
1414
t.Parallel()
1515

16-
cmd := func() *serpent.Command { return SampleCommand(t) }
16+
cmd := func() *serpent.Command { return sampleCommand(t) }
1717

1818
t.Run("SubcommandList", func(t *testing.T) {
1919
t.Parallel()
@@ -100,7 +100,7 @@ func TestCompletion(t *testing.T) {
100100
func TestFileCompletion(t *testing.T) {
101101
t.Parallel()
102102

103-
cmd := func() *serpent.Command { return SampleCommand(t) }
103+
cmd := func() *serpent.Command { return sampleCommand(t) }
104104

105105
t.Run("DirOK", func(t *testing.T) {
106106
t.Parallel()

example/completetest/main.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
"github.com/coder/serpent/completion"
99
)
1010

11-
// InstallCommand returns a serpent command that helps
11+
// installCommand returns a serpent command that helps
1212
// a user configure their shell to use serpent's completion.
13-
func InstallCommand() *serpent.Command {
14-
defaultShell, err := completion.GetUserShell()
13+
func installCommand() *serpent.Command {
14+
defaultShell, err := completion.DetectUserShell()
1515
if err != nil {
1616
defaultShell = "bash"
1717
}
@@ -21,7 +21,7 @@ func InstallCommand() *serpent.Command {
2121
Use: "completion",
2222
Short: "Generate completion scripts for the given shell.",
2323
Handler: func(inv *serpent.Invocation) error {
24-
completion.GetCompletion(inv.Stdout, shell, inv.Command.Parent.Name())
24+
completion.WriteCompletion(inv.Stdout, shell, inv.Command.Parent.Name())
2525
return nil
2626
},
2727
Options: serpent.OptionSet{
@@ -116,7 +116,7 @@ func main() {
116116
CompletionHandler: completion.FileHandler(nil),
117117
Middleware: serpent.RequireNArgs(1),
118118
},
119-
InstallCommand(),
119+
installCommand(),
120120
},
121121
}
122122

0 commit comments

Comments
 (0)