From 762447fe5e3e89193f37b19876c6ff95cb6afca5 Mon Sep 17 00:00:00 2001 From: rsteube Date: Sat, 2 May 2020 00:01:30 +0200 Subject: [PATCH] added wildcard completion --- carapace.go | 70 ++++++++++++++++++++++++++-------------- elvish/snippet.go | 4 +++ example/cmd/callback.go | 6 ++++ example/cmd/root_test.go | 29 +++++++++-------- zsh/snippet.go | 9 +++++- zsh/snippet_test.go | 4 +-- 6 files changed, 81 insertions(+), 41 deletions(-) diff --git a/carapace.go b/carapace.go index 1525a78cc..1b1c99990 100644 --- a/carapace.go +++ b/carapace.go @@ -44,6 +44,10 @@ func (c Carapace) PositionalCompletion(action ...Action) { } } +func (c Carapace) PositionalAnyCompletion(action Action) { + completions.actions[uid.Positional(c.cmd, 0)] = action.finalize(c.cmd, uid.Positional(c.cmd, 0)) +} + func (c Carapace) FlagCompletion(actions ActionMap) { for name, action := range actions { if flag := c.cmd.LocalFlags().Lookup(name); flag == nil { @@ -147,36 +151,25 @@ func addCompletionCommand(cmd *cobra.Command) { fmt.Println(Gen(cmd).Snippet(args[0])) } } else { - callback := args[1] - origArg := []string{} - if len(os.Args) > 5 { - origArg = os.Args[5:] - } - targetCmd, targetArgs := traverse(cmd, origArg) - if callback == "_" { - if len(targetArgs) == 0 { - callback = uid.Positional(targetCmd, 1) - } else { - lastArg := targetArgs[len(targetArgs)-1] - if strings.HasSuffix(lastArg, " ") { - callback = uid.Positional(targetCmd, len(targetArgs)+1) - } else { - callback = uid.Positional(targetCmd, len(targetArgs)) - } - } - if action, ok := completions.actions[callback]; !ok { - os.Exit(0) // ensure no message for missing action on positional completion // TODO this was only for bash, maybe enable for other shells? - } else { + targetCmd, targetArgs := findTarget(cmd) + + shell := args[0] + id := args[1] + + switch id { + case "_": + if action, ok := findAction(targetCmd, targetArgs); ok { if action.Callback == nil { - fmt.Println(action.Value(args[0])) - os.Exit(0) + fmt.Println(action.Value(shell)) + } else { + fmt.Println(action.Callback(targetArgs).Value(shell)) } } - } else if callback == "state" { + case "state": fmt.Println(uid.Command(targetCmd)) - os.Exit(0) // TODO + default: + fmt.Println(completions.invokeCallback(id, targetArgs).Value(shell)) } - fmt.Println(completions.invokeCallback(callback, targetArgs).Value(args[0])) } } }, @@ -187,6 +180,33 @@ func addCompletionCommand(cmd *cobra.Command) { }) } +func findAction(targetCmd *cobra.Command, targetArgs []string) (action Action, ok bool) { + var id string + if len(targetArgs) == 0 { + id = uid.Positional(targetCmd, 1) + } else { + lastArg := targetArgs[len(targetArgs)-1] + if strings.HasSuffix(lastArg, " ") { + id = uid.Positional(targetCmd, len(targetArgs)+1) + } else { + id = uid.Positional(targetCmd, len(targetArgs)) + } + } + if action, ok = completions.actions[id]; !ok { + id = uid.Positional(targetCmd, 0) + action, ok = completions.actions[id] + } + return +} + +func findTarget(cmd *cobra.Command) (*cobra.Command, []string) { + origArg := []string{} + if len(os.Args) > 5 { + origArg = os.Args[5:] + } + return traverse(cmd, origArg) +} + func traverse(cmd *cobra.Command, args []string) (*cobra.Command, []string) { // ignore flag parse errors (like a missing argument for the flag currently being completed) targetCmd, targetArgs, _ := cmd.Root().Traverse(args) diff --git a/elvish/snippet.go b/elvish/snippet.go index 5428afdc2..d6bcfde1d 100644 --- a/elvish/snippet.go +++ b/elvish/snippet.go @@ -92,6 +92,10 @@ func snippetFunctions(cmd *cobra.Command, actions map[string]string) string { break // TODO only consistent entries for now } } + if action, ok := actions[uid.Positional(cmd, 0)]; ok { + positionals = append(positionals, " "+snippetPositionalCompletion(action)) + positionals = append(positionals, " "+"...") + } if len(positionals) == 0 { if cmd.ValidArgs != nil { positionals = []string{" " + snippetPositionalCompletion(ActionValues(cmd.ValidArgs...))} diff --git a/example/cmd/callback.go b/example/cmd/callback.go index 727d43157..b8212c94c 100644 --- a/example/cmd/callback.go +++ b/example/cmd/callback.go @@ -30,4 +30,10 @@ func init() { return carapace.ActionValues("callback1", "callback2") }), ) + + carapace.Gen(callbackCmd).PositionalAnyCompletion( + carapace.ActionCallback(func(args []string) carapace.Action { + return carapace.ActionMessage(fmt.Sprintf("POS_%v", len(args))) + }), + ) } diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go index 13cb64534..aff005be9 100644 --- a/example/cmd/root_test.go +++ b/example/cmd/root_test.go @@ -264,6 +264,8 @@ edit:complex-candidate example &display-suffix=' (exampleDescription)' ] arg-handlers = [ [_]{ _example_callback '_example__callback#1' } + [_]{ _example_callback '_example__callback#0' } + ... ] subargs = $arg[(subindex callback):] if (> (count $subargs) 0) { @@ -672,20 +674,21 @@ function _example__action { "(-u --users)"{-u,--users}"[users flag]: :_users" \ "(-v --values)"{-v,--values}"[values flag]: :_values '' values example" \ "(-d --values_described)"{-d,--values_described}"[values with description flag]: :_values '' 'values[valueDescription]' 'example[exampleDescription]' " \ - "1:: :_values '' positional1 p1" \ - "2:: :_values '' positional2 p2" + "1: :_values '' positional1 p1" \ + "2: :_values '' positional2 p2" } function _example__callback { _arguments -C \ "(-c --callback)"{-c,--callback}"[Help message for callback]: : eval \$(example _carapace zsh '_example__callback##callback' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" \ - "1:: : eval \$(example _carapace zsh '_example__callback#1' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" + "1: : eval \$(example _carapace zsh '_example__callback#1' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" \ + "*: : eval \$(example _carapace zsh '_example__callback#0' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" } function _example__condition { _arguments -C \ "(-r --required)"{-r,--required}"[required flag]: :_values '' valid invalid" \ - "1:: : eval \$(example _carapace zsh '_example__condition#1' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" + "1: : eval \$(example _carapace zsh '_example__condition#1' ${${os_args:1:gs/\"/\\\"}:gs/\'/\\\"})" } function _example__help { @@ -695,15 +698,15 @@ function _example__help { function _example__injection { _arguments -C \ - "1:: :_values '' echo\ fail" \ - "2:: :_values '' echo\ fail" \ - "3:: :_values '' echo\ fail" \ - "4:: :_values '' \ echo\ fail\ " \ - "5:: :_values '' \ echo\ fail\ " \ - "6:: :_values '' \ echo\ fail\ " \ - "7:: :_values '' echo\ fail" \ - "8:: : _message -r 'no values to complete'" \ - "9:: :_values '' LAST\ POSITIONAL\ VALUE" + "1: :_values '' echo\ fail" \ + "2: :_values '' echo\ fail" \ + "3: :_values '' echo\ fail" \ + "4: :_values '' \ echo\ fail\ " \ + "5: :_values '' \ echo\ fail\ " \ + "6: :_values '' \ echo\ fail\ " \ + "7: :_values '' echo\ fail" \ + "8: : _message -r 'no values to complete'" \ + "9: :_values '' LAST\ POSITIONAL\ VALUE" } if compquote '' 2>/dev/null; then _example; else compdef _example example; fi ` diff --git a/zsh/snippet.go b/zsh/snippet.go index f6308fb66..3d60cd528 100644 --- a/zsh/snippet.go +++ b/zsh/snippet.go @@ -65,6 +65,9 @@ func snippetFunctions(cmd *cobra.Command, actions map[string]string) string { positionals = append(positionals, " "+snippetPositionalCompletion(pos, action)) pos++ } else { + if action, ok := actions[uid.Positional(cmd, 0)]; ok { + positionals = append(positionals, " "+snippetPositionalAnyCompletion(action)) + } break // TODO only consisten entriess for now } } @@ -115,7 +118,11 @@ func snippetFlagCompletion(flag *pflag.Flag, action *string) (snippet string) { } func snippetPositionalCompletion(position int, action string) string { - return fmt.Sprintf(`"%v:: :%v"`, position, action) + return fmt.Sprintf(`"%v: :%v"`, position, action) +} + +func snippetPositionalAnyCompletion(action string) string { + return fmt.Sprintf(`"*: :%v"`, action) } func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { diff --git a/zsh/snippet_test.go b/zsh/snippet_test.go index e597a7487..c5c85dae6 100644 --- a/zsh/snippet_test.go +++ b/zsh/snippet_test.go @@ -30,10 +30,10 @@ func TestSnippetFlagCompletion(t *testing.T) { func TestSnippetPositionalCompletion(t *testing.T) { pos1 := snippetPositionalCompletion(1, ActionValues("a", "b", "c")) - assert.Equal(t, `"1:: :_values '' a b c"`, pos1) + assert.Equal(t, `"1: :_values '' a b c"`, pos1) pos2 := snippetPositionalCompletion(2, ActionMessage("test")) - assert.Equal(t, `"2:: : _message -r 'test'"`, pos2) + assert.Equal(t, `"2: : _message -r 'test'"`, pos2) } func TestSnippetSubcommands(t *testing.T) {