From b20e52ae5911c71dcfe1eaf08168f02ba8077245 Mon Sep 17 00:00:00 2001 From: rsteube Date: Sat, 24 Feb 2024 16:00:33 +0100 Subject: [PATCH] bridge: alle empty arguments --- pkg/actions/bridge/argcomplete.go | 120 ++++++++++++++-------------- pkg/actions/bridge/bash.go | 90 ++++++++++----------- pkg/actions/bridge/bridge.go | 108 ++++++++++++------------- pkg/actions/bridge/carapace.go | 26 +++--- pkg/actions/bridge/clap.go | 38 +++++---- pkg/actions/bridge/click.go | 62 +++++++------- pkg/actions/bridge/cobra.go | 72 ++++++++--------- pkg/actions/bridge/complete.go | 32 ++++---- pkg/actions/bridge/fish.go | 72 ++++++++--------- pkg/actions/bridge/inshellisense.go | 98 +++++++++++------------ pkg/actions/bridge/kingpin.go | 42 +++++----- pkg/actions/bridge/powershell.go | 112 +++++++++++++------------- pkg/actions/bridge/urfavecli.go | 26 +++--- pkg/actions/bridge/yargs.go | 58 +++++++------- pkg/actions/bridge/zsh.go | 102 ++++++++++++----------- 15 files changed, 514 insertions(+), 544 deletions(-) diff --git a/pkg/actions/bridge/argcomplete.go b/pkg/actions/bridge/argcomplete.go index 8cbea8b..d252de8 100644 --- a/pkg/actions/bridge/argcomplete.go +++ b/pkg/actions/bridge/argcomplete.go @@ -29,77 +29,75 @@ import ( // ) // } func ActionArgcomplete(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionArgcomplete]") - } - - if _, err := exec.LookPath(command[0]); err != nil { - return carapace.ActionMessage(err.Error()) - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if _, err := exec.LookPath(command[0]); err != nil { + return carapace.ActionMessage(err.Error()) + } - args := append(command[1:], c.Args...) - current := c.Value + args := append(command[1:], c.Args...) + current := c.Value - prefix := "" - if strings.HasPrefix(current, "--") { - if strings.Contains(current, "=") { // optarg flag which is handled as normal arg by the completer - splitted := strings.SplitN(current, "=", 2) - prefix = splitted[0] + "=" - args = append(args, splitted[0]) // add flag as arg - current = "" // seem partial optarg value isn't completed + prefix := "" + if strings.HasPrefix(current, "--") { + if strings.Contains(current, "=") { // optarg flag which is handled as normal arg by the completer + splitted := strings.SplitN(current, "=", 2) + prefix = splitted[0] + "=" + args = append(args, splitted[0]) // add flag as arg + current = "" // seem partial optarg value isn't completed + } else { + current = "--" // seems partial flag names aren't completed so get all + } } else { - current = "--" // seems partial flag names aren't completed so get all + current = "" // seems partial positional arguments aren't completed as well } - } else { - current = "" // seems partial positional arguments aren't completed as well - } - compLine := command[0] + " " + strings.Join(append(args, current), " ") // TODO escape/quote special characters - c.Setenv("_ARGCOMPLETE", "1") - c.Setenv("_ARGCOMPLETE_DFS", "\t") - c.Setenv("_ARGCOMPLETE_IFS", "\n") - c.Setenv("_ARGCOMPLETE_SHELL", "fish") - c.Setenv("_ARGCOMPLETE_SUPPRESS_SPACE", "1") // TODO needed? relevant for nospace detection? - // c.Setenv("_ARGCOMPLETE_COMP_WORDBREAKS", " ") // TODO set to space-only for multiparts? - c.Setenv("_ARGCOMPLETE", "1") - c.Setenv("COMP_LINE", compLine) - c.Setenv("COMP_POINT", strconv.Itoa(len(compLine))) - nospace := false - a := carapace.ActionExecCommand("sh", "-c", command[0]+" 8>&1 9>&2 1>/dev/null 2>/dev/null")(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - vals := make([]string, 0) - isFlag := strings.HasPrefix(c.Value, "-") - for _, line := range lines[:len(lines)-1] { - if !isFlag && strings.HasPrefix(line, "-") { - continue - } - if strings.HasSuffix(line, "=") || - strings.HasSuffix(line, "/") || - strings.HasSuffix(line, ",") { - nospace = true - } - if splitted := strings.SplitN(line, "\t", 2); splitted[0] != "" { - vals = append(vals, splitted...) - if len(splitted) < 2 { - vals = append(vals, "") + compLine := command[0] + " " + strings.Join(append(args, current), " ") // TODO escape/quote special characters + c.Setenv("_ARGCOMPLETE", "1") + c.Setenv("_ARGCOMPLETE_DFS", "\t") + c.Setenv("_ARGCOMPLETE_IFS", "\n") + c.Setenv("_ARGCOMPLETE_SHELL", "fish") + c.Setenv("_ARGCOMPLETE_SUPPRESS_SPACE", "1") // TODO needed? relevant for nospace detection? + // c.Setenv("_ARGCOMPLETE_COMP_WORDBREAKS", " ") // TODO set to space-only for multiparts? + c.Setenv("_ARGCOMPLETE", "1") + c.Setenv("COMP_LINE", compLine) + c.Setenv("COMP_POINT", strconv.Itoa(len(compLine))) + nospace := false + a := carapace.ActionExecCommand("sh", "-c", command[0]+" 8>&1 9>&2 1>/dev/null 2>/dev/null")(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + vals := make([]string, 0) + isFlag := strings.HasPrefix(c.Value, "-") + for _, line := range lines[:len(lines)-1] { + if !isFlag && strings.HasPrefix(line, "-") { + continue + } + if strings.HasSuffix(line, "=") || + strings.HasSuffix(line, "/") || + strings.HasSuffix(line, ",") { + nospace = true + } + if splitted := strings.SplitN(line, "\t", 2); splitted[0] != "" { + vals = append(vals, splitted...) + if len(splitted) < 2 { + vals = append(vals, "") + } } } - } - if len(vals) == 0 { - // fallback to file completions when no values returned - if index := strings.Index(c.Value, "="); index > -1 { - return carapace.ActionFiles().Invoke(carapace.Context{Value: c.Value[index+1:]}).ToA() + if len(vals) == 0 { + // fallback to file completions when no values returned + if index := strings.Index(c.Value, "="); index > -1 { + return carapace.ActionFiles().Invoke(carapace.Context{Value: c.Value[index+1:]}).ToA() + } + return carapace.ActionFiles() } - return carapace.ActionFiles() + return carapace.ActionValuesDescribed(vals...) + }).Invoke(c).Prefix(prefix).ToA() // re-add optarg prefix + if nospace { + return a.NoSpace() } - return carapace.ActionValuesDescribed(vals...) - }).Invoke(c).Prefix(prefix).ToA() // re-add optarg prefix - if nospace { - return a.NoSpace() - } - return a + return a + }) }) } diff --git a/pkg/actions/bridge/bash.go b/pkg/actions/bridge/bash.go index fa19349..7eb2c14 100644 --- a/pkg/actions/bridge/bash.go +++ b/pkg/actions/bridge/bash.go @@ -18,59 +18,57 @@ var bashSnippet string // ActionBash bridges completions registered in bash // (uses custom `.bashrc` in “~/.config/carapace/bridge/bash`) func ActionBash(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionBash]") - } - - configDir, err := xdg.UserConfigDir() - if err != nil { - return carapace.ActionMessage(err.Error()) - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + configDir, err := xdg.UserConfigDir() + if err != nil { + return carapace.ActionMessage(err.Error()) + } - args := append(command, c.Args...) - args = append(args, c.Value) + args := append(command, c.Args...) + args = append(args, c.Value) - configPath := fmt.Sprintf("%v/carapace/bridge/bash/.bashrc", configDir) - if err := ensureExists(configPath); err != nil { - return carapace.ActionMessage(err.Error()) - } + configPath := fmt.Sprintf("%v/carapace/bridge/bash/.bashrc", configDir) + if err := ensureExists(configPath); err != nil { + return carapace.ActionMessage(err.Error()) + } - joined := shlex.Join(args) - if c.Value == "" { - joined = strings.TrimSuffix(joined, `""`) - } - c.Setenv("COMP_LINE", joined) + joined := shlex.Join(args) + if c.Value == "" { + joined = strings.TrimSuffix(joined, `""`) + } + c.Setenv("COMP_LINE", joined) - file, err := os.CreateTemp(os.TempDir(), "carapace-bridge_bash_*") - if err != nil { - return carapace.ActionMessage(err.Error()) - } - defer os.Remove(file.Name()) + file, err := os.CreateTemp(os.TempDir(), "carapace-bridge_bash_*") + if err != nil { + return carapace.ActionMessage(err.Error()) + } + defer os.Remove(file.Name()) - os.WriteFile(file.Name(), []byte(bashSnippet), os.ModePerm) + os.WriteFile(file.Name(), []byte(bashSnippet), os.ModePerm) - return carapace.ActionExecCommand("bash", "--rcfile", configPath, "-i", file.Name())(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") + return carapace.ActionExecCommand("bash", "--rcfile", configPath, "-i", file.Name())(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - vals := make([]string, 0) - for _, line := range lines[:len(lines)-1] { - if splitted := strings.SplitN(line, "(", 2); len(splitted) == 2 { - // assume results contain descriptions in the format `value (description)` (spf13/cobra, rsteube/carapace) - vals = append(vals, - strings.TrimSpace(splitted[0]), - strings.TrimSpace(strings.TrimSuffix(splitted[1], ")")), - ) - } else { - vals = append(vals, strings.TrimSpace(line), "") + vals := make([]string, 0) + for _, line := range lines[:len(lines)-1] { + if splitted := strings.SplitN(line, "(", 2); len(splitted) == 2 { + // assume results contain descriptions in the format `value (description)` (spf13/cobra, rsteube/carapace) + vals = append(vals, + strings.TrimSpace(splitted[0]), + strings.TrimSpace(strings.TrimSuffix(splitted[1], ")")), + ) + } else { + vals = append(vals, strings.TrimSpace(line), "") + } } - } - switch len(vals) { - case 0: - return carapace.ActionFiles() - default: - return carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) - } - }).Invoke(c).ToA().NoSpace([]rune("/=@:.,")...) // TODO check compopt for nospace + switch len(vals) { + case 0: + return carapace.ActionFiles() + default: + return carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) + } + }).Invoke(c).ToA().NoSpace([]rune("/=@:.,")...) // TODO check compopt for nospace + }) }) } diff --git a/pkg/actions/bridge/bridge.go b/pkg/actions/bridge/bridge.go index fd24cb5..d31e7fe 100644 --- a/pkg/actions/bridge/bridge.go +++ b/pkg/actions/bridge/bridge.go @@ -10,66 +10,64 @@ import ( // Bridges bridges completions as defined in bridges.yaml and CARAPACE_BRIDGE environment variable func ActionBridges(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionBridges]") - } - - if bridge, ok := bridges.Config()[command[0]]; ok { - switch bridge { - case "argcomplete": - return ActionArgcomplete(command...) - case "bash": - return ActionBash(command...) - case "carapace": - return ActionCarapace(command...) - case "clap": - return ActionClap(command...) - case "click": - return ActionClick(command...) - case "cobra": - return ActionCobra(command...) - case "complete": - return ActionComplete(command...) - case "fish": - return ActionFish(command...) - case "inshellisense": - return ActionInshellisense(command...) - case "kingpin": - return ActionKingpin(command...) - case "powershell": - return ActionPowershell(command...) - case "urfavecli": - return ActionUrfavecli(command...) - case "yargs": - return ActionYargs(command...) - case "zsh": - return ActionZsh(command...) - default: - return carapace.ActionMessage("unknown bridge: %v", bridge) - } - } - - for _, b := range env.Bridges() { - switch b { - case "bash": - if slices.Contains(bridges.Bash(), command[0]) { + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if bridge, ok := bridges.Config()[command[0]]; ok { + switch bridge { + case "argcomplete": + return ActionArgcomplete(command...) + case "bash": return ActionBash(command...) - } - case "fish": - if slices.Contains(bridges.Fish(), command[0]) { + case "carapace": + return ActionCarapace(command...) + case "clap": + return ActionClap(command...) + case "click": + return ActionClick(command...) + case "cobra": + return ActionCobra(command...) + case "complete": + return ActionComplete(command...) + case "fish": return ActionFish(command...) - } - case "inshellisense": - if slices.Contains(bridges.Inshellisense(), command[0]) { + case "inshellisense": return ActionInshellisense(command...) - } - case "zsh": - if slices.Contains(bridges.Zsh(), command[0]) { + case "kingpin": + return ActionKingpin(command...) + case "powershell": + return ActionPowershell(command...) + case "urfavecli": + return ActionUrfavecli(command...) + case "yargs": + return ActionYargs(command...) + case "zsh": return ActionZsh(command...) + default: + return carapace.ActionMessage("unknown bridge: %v", bridge) + } + } + + for _, b := range env.Bridges() { + switch b { + case "bash": + if slices.Contains(bridges.Bash(), command[0]) { + return ActionBash(command...) + } + case "fish": + if slices.Contains(bridges.Fish(), command[0]) { + return ActionFish(command...) + } + case "inshellisense": + if slices.Contains(bridges.Inshellisense(), command[0]) { + return ActionInshellisense(command...) + } + case "zsh": + if slices.Contains(bridges.Zsh(), command[0]) { + return ActionZsh(command...) + } } } - } - return carapace.ActionValues() + return carapace.ActionValues() + }) }) } diff --git a/pkg/actions/bridge/carapace.go b/pkg/actions/bridge/carapace.go index 1c8bf1a..936e405 100644 --- a/pkg/actions/bridge/carapace.go +++ b/pkg/actions/bridge/carapace.go @@ -11,20 +11,18 @@ import ( // ActionCarapace bridges https://github.com/rsteube/carapace func ActionCarapace(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionCarapace]") - } - - args := []string{"_carapace", "export", ""} - args = append(args, command[1:]...) - args = append(args, c.Args...) - args = append(args, c.Value) - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - if string(output) == "" { - return carapace.ActionValues() - } - return carapace.ActionImport(output) + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"_carapace", "export", ""} + args = append(args, command[1:]...) + args = append(args, c.Args...) + args = append(args, c.Value) + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + if string(output) == "" { + return carapace.ActionValues() + } + return carapace.ActionImport(output) + }) }) }) } diff --git a/pkg/actions/bridge/clap.go b/pkg/actions/bridge/clap.go index 9cac89d..e678ad8 100644 --- a/pkg/actions/bridge/clap.go +++ b/pkg/actions/bridge/clap.go @@ -28,27 +28,25 @@ import ( // ) // } func ActionClap(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionClap]") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + index := len(append(command, c.Args...)) + args := []string{"complete", "--index", strconv.Itoa(index), "--type", "9", "--no-space", "--ifs=\n", "--"} + args = append(args, command...) + args = append(args, c.Args...) + args = append(args, c.Value) + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - index := len(append(command, c.Args...)) - args := []string{"complete", "--index", strconv.Itoa(index), "--type", "9", "--no-space", "--ifs=\n", "--"} - args = append(args, command...) - args = append(args, c.Args...) - args = append(args, c.Value) - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - - a := carapace.ActionValues(lines...) - for _, line := range lines { - if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { - a = a.NoSpace() - break + a := carapace.ActionValues(lines...) + for _, line := range lines { + if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { + a = a.NoSpace() + break + } } - } - return a - }).Invoke(c).ToA() + return a + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/click.go b/pkg/actions/bridge/click.go index ab6bfa3..6f46e6b 100644 --- a/pkg/actions/bridge/click.go +++ b/pkg/actions/bridge/click.go @@ -31,42 +31,40 @@ import ( // ) // } func ActionClick(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionClick]") - } - - if _, err := exec.LookPath(command[0]); err != nil { - return carapace.ActionMessage(err.Error()) - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if _, err := exec.LookPath(command[0]); err != nil { + return carapace.ActionMessage(err.Error()) + } - args := append(command[1:], c.Args...) - current := c.Value + args := append(command[1:], c.Args...) + current := c.Value - compLine := command[0] + " " + shlex.Join(append(args, current)) - c.Setenv(fmt.Sprintf("_%v_COMPLETE", strings.ToUpper(command[0])), "zsh_complete") - c.Setenv("COMP_WORDS", compLine) - c.Setenv("COMP_CWORD", strconv.Itoa(len(args)+1)) - return carapace.ActionExecCommand(command[0])(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") + compLine := command[0] + " " + shlex.Join(append(args, current)) + c.Setenv(fmt.Sprintf("_%v_COMPLETE", strings.ToUpper(command[0])), "zsh_complete") + c.Setenv("COMP_WORDS", compLine) + c.Setenv("COMP_CWORD", strconv.Itoa(len(args)+1)) + return carapace.ActionExecCommand(command[0])(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - vals := make([]string, 0) - for i := 0; i+3 < len(lines); i += 3 { - switch lines[i] { // type - case "dir": - return carapace.ActionDirectories() - case "file": - return carapace.ActionFiles() - case "plain": - value := lines[i+1] - description := lines[i+2] - if description == "_" { - description = "" + vals := make([]string, 0) + for i := 0; i+3 < len(lines); i += 3 { + switch lines[i] { // type + case "dir": + return carapace.ActionDirectories() + case "file": + return carapace.ActionFiles() + case "plain": + value := lines[i+1] + description := lines[i+2] + if description == "_" { + description = "" + } + vals = append(vals, value, description) } - vals = append(vals, value, description) } - } - return carapace.ActionValuesDescribed(vals...) - }).Invoke(c).ToA() + return carapace.ActionValuesDescribed(vals...) + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/cobra.go b/pkg/actions/bridge/cobra.go index 62f722b..d44fc1c 100644 --- a/pkg/actions/bridge/cobra.go +++ b/pkg/actions/bridge/cobra.go @@ -29,48 +29,46 @@ import ( // ) // } func ActionCobra(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionCobra]") - } - - args := []string{"__complete"} - args = append(args, command[1:]...) - args = append(args, c.Args...) - args = append(args, c.Value) - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - if len(lines) == 0 { - return carapace.ActionMessage("unexpected command output") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"__complete"} + args = append(args, command[1:]...) + args = append(args, c.Args...) + args = append(args, c.Value) + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + if len(lines) == 0 { + return carapace.ActionMessage("unexpected command output") + } - // TODO experimental - directives not yet fully tested - var action carapace.Action - directive, err := readDirective(lines) - if err != nil { - return carapace.ActionValues() - } + // TODO experimental - directives not yet fully tested + var action carapace.Action + directive, err := readDirective(lines) + if err != nil { + return carapace.ActionValues() + } - if directive.matches(cobra.ShellCompDirectiveFilterDirs) { - return actionDirectories(lines) - } else if directive.matches(cobra.ShellCompDirectiveFilterFileExt) { - return actionFiles(lines) - } else { - action = actionValues(lines) - } + if directive.matches(cobra.ShellCompDirectiveFilterDirs) { + return actionDirectories(lines) + } else if directive.matches(cobra.ShellCompDirectiveFilterFileExt) { + return actionFiles(lines) + } else { + action = actionValues(lines) + } - if len(lines) < 3 && !directive.matches(cobra.ShellCompDirectiveNoFileComp) { - action = carapace.ActionFiles() - } + if len(lines) < 3 && !directive.matches(cobra.ShellCompDirectiveNoFileComp) { + action = carapace.ActionFiles() + } - if directive.matches(cobra.ShellCompDirectiveError) { - action = carapace.ActionValues() - } + if directive.matches(cobra.ShellCompDirectiveError) { + action = carapace.ActionValues() + } - if directive.matches(cobra.ShellCompDirectiveNoSpace) { - action = action.NoSpace() - } - return action + if directive.matches(cobra.ShellCompDirectiveNoSpace) { + action = action.NoSpace() + } + return action + }) }) }) } diff --git a/pkg/actions/bridge/complete.go b/pkg/actions/bridge/complete.go index 3038c3c..89da892 100644 --- a/pkg/actions/bridge/complete.go +++ b/pkg/actions/bridge/complete.go @@ -28,24 +28,22 @@ import ( // ) // } func ActionComplete(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionComplete]") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + c.Args = append(command[1:], c.Args...) + c.Setenv("COMP_LINE", fmt.Sprintf("%v %v %v", command[0], strings.Join(c.Args, " "), c.Value)) + return carapace.ActionExecCommand(command[0])(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - c.Args = append(command[1:], c.Args...) - c.Setenv("COMP_LINE", fmt.Sprintf("%v %v %v", command[0], strings.Join(c.Args, " "), c.Value)) - return carapace.ActionExecCommand(command[0])(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - - a := carapace.ActionValues(lines[:len(lines)-1]...) - for _, line := range lines[:len(lines)-1] { - if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { - a = a.NoSpace() - break + a := carapace.ActionValues(lines[:len(lines)-1]...) + for _, line := range lines[:len(lines)-1] { + if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { + a = a.NoSpace() + break + } } - } - return a - }).Invoke(c).ToA() + return a + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/fish.go b/pkg/actions/bridge/fish.go index 21697f0..5b7f21c 100644 --- a/pkg/actions/bridge/fish.go +++ b/pkg/actions/bridge/fish.go @@ -13,44 +13,42 @@ import ( // ActionFish bridges completions registered in fish // (uses custom `config.fish` in “~/.config/carapace/bridge/fish`) func ActionFish(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionFish]") - } - - configDir, err := xdg.UserConfigDir() - if err != nil { - return carapace.ActionMessage(err.Error()) - } - - args := append(command, c.Args...) - args = append(args, c.Value) - - configPath := fmt.Sprintf("%v/carapace/bridge/fish/config.fish", configDir) - if err := ensureExists(configPath); err != nil { - return carapace.ActionMessage(err.Error()) - } - - snippet := fmt.Sprintf(`source "$__fish_data_dir/config.fish";source %#v;complete --do-complete="%v"`, configPath, shlex.Join(args)) // TODO needs custom escaping - return carapace.ActionExecCommand("fish", "--no-config", "--command", snippet)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - - vals := make([]string, 0) - for _, line := range lines[:len(lines)-1] { - splitted := strings.SplitN(line, "\t", 2) - - if len(splitted) > 1 { - vals = append(vals, splitted...) - } else { - vals = append(vals, splitted[0], "") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + configDir, err := xdg.UserConfigDir() + if err != nil { + return carapace.ActionMessage(err.Error()) + } + + args := append(command, c.Args...) + args = append(args, c.Value) + + configPath := fmt.Sprintf("%v/carapace/bridge/fish/config.fish", configDir) + if err := ensureExists(configPath); err != nil { + return carapace.ActionMessage(err.Error()) } - return carapace.ActionValuesDescribed(vals...).StyleF(func(s string, sc style.Context) string { - if strings.HasPrefix(s, "--") && strings.Contains(s, "=") { - s = strings.SplitN(s, "=", 2)[1] // assume optarg + + snippet := fmt.Sprintf(`source "$__fish_data_dir/config.fish";source %#v;complete --do-complete="%v"`, configPath, shlex.Join(args)) // TODO needs custom escaping + return carapace.ActionExecCommand("fish", "--no-config", "--command", snippet)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + + vals := make([]string, 0) + for _, line := range lines[:len(lines)-1] { + splitted := strings.SplitN(line, "\t", 2) + + if len(splitted) > 1 { + vals = append(vals, splitted...) + } else { + vals = append(vals, splitted[0], "") + } } - return style.ForPath(s, sc) - }) - }).NoSpace([]rune("/=@:.,")...) + return carapace.ActionValuesDescribed(vals...).StyleF(func(s string, sc style.Context) string { + if strings.HasPrefix(s, "--") && strings.Contains(s, "=") { + s = strings.SplitN(s, "=", 2)[1] // assume optarg + } + return style.ForPath(s, sc) + }) + }).NoSpace([]rune("/=@:.,")...) + }) }) } diff --git a/pkg/actions/bridge/inshellisense.go b/pkg/actions/bridge/inshellisense.go index 601ce6d..6176d52 100644 --- a/pkg/actions/bridge/inshellisense.go +++ b/pkg/actions/bridge/inshellisense.go @@ -11,66 +11,64 @@ import ( // ActionInshellisense bridges https://github.com/microsoft/inshellisense func ActionInshellisense(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionInshellisense]") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := append(command, c.Args...) + args = append(args, c.Value) - args := append(command, c.Args...) - args = append(args, c.Value) + input := shlex.Join(args) - input := shlex.Join(args) - - if strings.HasSuffix(input, `""`) { - // TODO temporary fix as inshellisense can't handle quotes yet (won't work for those within) - input = input[:len(input)-2] + " " - } + if strings.HasSuffix(input, `""`) { + // TODO temporary fix as inshellisense can't handle quotes yet (won't work for those within) + input = input[:len(input)-2] + " " + } - return carapace.ActionExecCommand("inshellisense", "complete", input)(func(output []byte) carapace.Action { - var r struct { - Suggestions []struct { - Name string - AllNames []string `json:"allNames"` - Description string - Icon string + return carapace.ActionExecCommand("inshellisense", "complete", input)(func(output []byte) carapace.Action { + var r struct { + Suggestions []struct { + Name string + AllNames []string `json:"allNames"` + Description string + Icon string + } } - } - if err := json.Unmarshal(output, &r); err != nil { - return carapace.ActionMessage(err.Error()) - } + if err := json.Unmarshal(output, &r); err != nil { + return carapace.ActionMessage(err.Error()) + } - vals := make([]string, 0) - flags := make([]string, 0) - files := make([]string, 0) - for _, s := range r.Suggestions { - for _, name := range s.AllNames { - if !strings.HasPrefix(c.Value, "-") && strings.HasPrefix(name, "-") { - continue - } + vals := make([]string, 0) + flags := make([]string, 0) + files := make([]string, 0) + for _, s := range r.Suggestions { + for _, name := range s.AllNames { + if !strings.HasPrefix(c.Value, "-") && strings.HasPrefix(name, "-") { + continue + } - if strings.HasPrefix(name, c.Value) || - (strings.HasPrefix(c.Value, "-") && strings.Contains(c.Value, "=")) { - switch s.Icon { - case "📀": - files = append(files, name, s.Description) - case "🔗": - flags = append(flags, name, s.Description) - default: - vals = append(vals, name, s.Description) + if strings.HasPrefix(name, c.Value) || + (strings.HasPrefix(c.Value, "-") && strings.Contains(c.Value, "=")) { + switch s.Icon { + case "📀": + files = append(files, name, s.Description) + case "🔗": + flags = append(flags, name, s.Description) + default: + vals = append(vals, name, s.Description) + } } } } - } - a := carapace.Batch( - carapace.ActionValuesDescribed(vals...), - carapace.ActionValuesDescribed(flags...).Tag("flags"), - carapace.ActionValuesDescribed(files...).StyleF(style.ForPathExt).Tag("files"), - ).ToA() - if strings.HasPrefix(c.Value, "-") && strings.Contains(c.Value, "=") { - a = a.Prefix(strings.SplitAfterN(c.Value, "=", 2)[0]) - } - return a + a := carapace.Batch( + carapace.ActionValuesDescribed(vals...), + carapace.ActionValuesDescribed(flags...).Tag("flags"), + carapace.ActionValuesDescribed(files...).StyleF(style.ForPathExt).Tag("files"), + ).ToA() + if strings.HasPrefix(c.Value, "-") && strings.Contains(c.Value, "=") { + a = a.Prefix(strings.SplitAfterN(c.Value, "=", 2)[0]) + } + return a + }) }) }) } diff --git a/pkg/actions/bridge/kingpin.go b/pkg/actions/bridge/kingpin.go index 7163110..5f1338d 100644 --- a/pkg/actions/bridge/kingpin.go +++ b/pkg/actions/bridge/kingpin.go @@ -27,30 +27,28 @@ import ( // ) // } func ActionKingpin(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionKingpin]") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"--completion-bash"} + args = append(args, command[1:]...) + args = append(args, c.Args...) + args = append(args, c.Value) + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - args := []string{"--completion-bash"} - args = append(args, command[1:]...) - args = append(args, c.Args...) - args = append(args, c.Value) - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - - if len(lines) < 2 && !strings.HasPrefix(c.Value, "-") { - return carapace.ActionFiles() - } + if len(lines) < 2 && !strings.HasPrefix(c.Value, "-") { + return carapace.ActionFiles() + } - a := carapace.ActionValues(lines...) - for _, line := range lines { - if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { - a = a.NoSpace() - break + a := carapace.ActionValues(lines...) + for _, line := range lines { + if len(line) > 0 && strings.ContainsAny(line[:len(line)-1], "/=@:.,") { + a = a.NoSpace() + break + } } - } - return a - }).Invoke(c).ToA() + return a + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/powershell.go b/pkg/actions/bridge/powershell.go index 2e9591b..6ffa54c 100644 --- a/pkg/actions/bridge/powershell.go +++ b/pkg/actions/bridge/powershell.go @@ -27,72 +27,70 @@ func ensureExists(path string) (err error) { // ActionPowershell bridges completions registered in powershell // (uses custom `Microsoft.PowerShell_profile.ps1` in “~/.config/carapace/bridge/powershell`) func ActionPowershell(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionPowershell]") - } - - configDir, err := xdg.UserConfigDir() - if err != nil { - return carapace.ActionMessage(err.Error()) - } - configPath := fmt.Sprintf("%v/carapace/bridge/powershell/Microsoft.PowerShell_profile.ps1", configDir) - if err := ensureExists(configPath); err != nil { - return carapace.ActionMessage(err.Error()) - } - - args := append(command, c.Args...) - args = append(args, c.Value) + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + configDir, err := xdg.UserConfigDir() + if err != nil { + return carapace.ActionMessage(err.Error()) + } + configPath := fmt.Sprintf("%v/carapace/bridge/powershell/Microsoft.PowerShell_profile.ps1", configDir) + if err := ensureExists(configPath); err != nil { + return carapace.ActionMessage(err.Error()) + } - // for index, arg := range args { - // TODO handle different escape character and escaping in general - // args[index] = strings.Replace(arg, " ", "` ", -1) - // } + args := append(command, c.Args...) + args = append(args, c.Value) - line := shlex.Join(args) - snippet := []string{ - fmt.Sprintf(`Get-Content "%v/carapace/bridge/powershell/Microsoft.PowerShell_profile.ps1" | Out-String | Invoke-Expression`, configDir), - fmt.Sprintf(`[System.Management.Automation.CommandCompletion]::CompleteInput("%v", %v, $null).CompletionMatches | ConvertTo-Json `, line, len(line)), - } - return carapace.ActionExecCommand("pwsh", "-Command", strings.Join(snippet, ";"))(func(output []byte) carapace.Action { - if len(output) == 0 { - return carapace.ActionValues() - } + // for index, arg := range args { + // TODO handle different escape character and escaping in general + // args[index] = strings.Replace(arg, " ", "` ", -1) + // } - type singleResult struct { - CompletionText string `json:"CompletionText"` - ListItemText string `json:"ListItemText"` - ResultType int `json:"ResultType"` - ToolTip string `json:"ToolTip"` + line := shlex.Join(args) + snippet := []string{ + fmt.Sprintf(`Get-Content "%v/carapace/bridge/powershell/Microsoft.PowerShell_profile.ps1" | Out-String | Invoke-Expression`, configDir), + fmt.Sprintf(`[System.Management.Automation.CommandCompletion]::CompleteInput("%v", %v, $null).CompletionMatches | ConvertTo-Json `, line, len(line)), } - var result []singleResult + return carapace.ActionExecCommand("pwsh", "-Command", strings.Join(snippet, ";"))(func(output []byte) carapace.Action { + if len(output) == 0 { + return carapace.ActionValues() + } - if err := json.Unmarshal(output, &result); err != nil { - result = make([]singleResult, 1) - if err := json.Unmarshal(output, &result[0]); err != nil { - carapace.LOG.Println(string(output)) - return carapace.ActionMessage(err.Error()) + type singleResult struct { + CompletionText string `json:"CompletionText"` + ListItemText string `json:"ListItemText"` + ResultType int `json:"ResultType"` + ToolTip string `json:"ToolTip"` } - } + var result []singleResult - suffixes := make([]rune, 0) - vals := make([]string, 0) - for _, r := range result { - if _runes := []rune(r.CompletionText); len(_runes) > 2 && strings.HasSuffix(r.CompletionText, " ") { - suffixes = append(suffixes, _runes[len(_runes)-1]) + if err := json.Unmarshal(output, &result); err != nil { + result = make([]singleResult, 1) + if err := json.Unmarshal(output, &result[0]); err != nil { + carapace.LOG.Println(string(output)) + return carapace.ActionMessage(err.Error()) + } } - r.CompletionText = strings.TrimSuffix(r.CompletionText, " ") - if r.CompletionText == r.ToolTip { - r.ToolTip = "" + suffixes := make([]rune, 0) + vals := make([]string, 0) + for _, r := range result { + if _runes := []rune(r.CompletionText); len(_runes) > 2 && strings.HasSuffix(r.CompletionText, " ") { + suffixes = append(suffixes, _runes[len(_runes)-1]) + } + r.CompletionText = strings.TrimSuffix(r.CompletionText, " ") + + if r.CompletionText == r.ToolTip { + r.ToolTip = "" + } + vals = append(vals, r.CompletionText, r.ToolTip) } - vals = append(vals, r.CompletionText, r.ToolTip) - } - a := carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) - if len(suffixes) > 0 { - return a.NoSpace(suffixes...) - } - return a - }).Invoke(c).ToA() + a := carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) + if len(suffixes) > 0 { + return a.NoSpace(suffixes...) + } + return a + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/urfavecli.go b/pkg/actions/bridge/urfavecli.go index b4b95df..9fcd898 100644 --- a/pkg/actions/bridge/urfavecli.go +++ b/pkg/actions/bridge/urfavecli.go @@ -8,20 +8,18 @@ import ( // ActionUrfavecli bridges https://github.com/urfave/cli func ActionUrfavecli(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionUrfavecli]") - } - - args := append(command[1:], c.Args...) - args = append(args, c.Value) - args = append(args, "--generate-bash-completion") - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") - if len(lines) <= 1 { - return carapace.ActionFiles() - } - return carapace.ActionValues(lines[:len(lines)-1]...).NoSpace([]rune("/=@:.,")...) + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := append(command[1:], c.Args...) + args = append(args, c.Value) + args = append(args, "--generate-bash-completion") + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") + if len(lines) <= 1 { + return carapace.ActionFiles() + } + return carapace.ActionValues(lines[:len(lines)-1]...).NoSpace([]rune("/=@:.,")...) + }) }) }) } diff --git a/pkg/actions/bridge/yargs.go b/pkg/actions/bridge/yargs.go index 103abd9..d0c24d2 100644 --- a/pkg/actions/bridge/yargs.go +++ b/pkg/actions/bridge/yargs.go @@ -28,40 +28,38 @@ import ( // ) // } func ActionYargs(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionYargs]") - } - - if _, err := exec.LookPath(command[0]); err != nil { - return carapace.ActionMessage(err.Error()) - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if _, err := exec.LookPath(command[0]); err != nil { + return carapace.ActionMessage(err.Error()) + } - c.Setenv("SHELL", "zsh") + c.Setenv("SHELL", "zsh") - args := []string{"--get-yargs-completions"} - args = append(args, command[1:]...) - args = append(args, c.Args...) - args = append(args, c.Value) - return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\n") + args := []string{"--get-yargs-completions"} + args = append(args, command[1:]...) + args = append(args, c.Args...) + args = append(args, c.Value) + return carapace.ActionExecCommand(command[0], args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\n") - vals := make([]string, 0) - for _, line := range lines { - if line != "" { - splitted := strings.SplitN(line, ":", 2) - name := splitted[0] - description := "" - if len(splitted) > 1 { - description = splitted[1] + vals := make([]string, 0) + for _, line := range lines { + if line != "" { + splitted := strings.SplitN(line, ":", 2) + name := splitted[0] + description := "" + if len(splitted) > 1 { + description = splitted[1] + } + vals = append(vals, name, description) } - vals = append(vals, name, description) } - } - if len(vals) == 0 { - return carapace.ActionFiles() - } - return carapace.ActionValuesDescribed(vals...) - }).Invoke(c).ToA() + if len(vals) == 0 { + return carapace.ActionFiles() + } + return carapace.ActionValuesDescribed(vals...) + }).Invoke(c).ToA() + }) }) } diff --git a/pkg/actions/bridge/zsh.go b/pkg/actions/bridge/zsh.go index 20e7643..2d98940 100644 --- a/pkg/actions/bridge/zsh.go +++ b/pkg/actions/bridge/zsh.go @@ -14,62 +14,60 @@ import ( // ActionZsh bridges completions registered in zsh // (uses custom `.zshrc` in “~/.config/carapace/bridge/zsh`) func ActionZsh(command ...string) carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - if len(command) == 0 { - return carapace.ActionMessage("missing argument [ActionZsh]") - } + return actionCommand(command...)(func(command ...string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + args := []string{"--no-rcs", "-c", zsh.Script, "--"} + args = append(args, command...) + args = append(args, c.Args...) + args = append(args, c.Value) - args := []string{"--no-rcs", "-c", zsh.Script, "--"} - args = append(args, command...) - args = append(args, c.Args...) - args = append(args, c.Value) - - configDir, err := xdg.UserConfigDir() - if err != nil { - return carapace.ActionMessage(err.Error()) - } - if err := ensureExists(configDir + "/carapace/bridge/zsh/.zshrc"); err != nil { - return carapace.ActionMessage(err.Error()) - } + configDir, err := xdg.UserConfigDir() + if err != nil { + return carapace.ActionMessage(err.Error()) + } + if err := ensureExists(configDir + "/carapace/bridge/zsh/.zshrc"); err != nil { + return carapace.ActionMessage(err.Error()) + } - c.Setenv("CARAPACE_BRIDGE_CONFIG_HOME", configDir) - return carapace.ActionExecCommand("zsh", args...)(func(output []byte) carapace.Action { - lines := strings.Split(string(output), "\r\n") - vals := make([]string, 0) + c.Setenv("CARAPACE_BRIDGE_CONFIG_HOME", configDir) + return carapace.ActionExecCommand("zsh", args...)(func(output []byte) carapace.Action { + lines := strings.Split(string(output), "\r\n") + vals := make([]string, 0) - var unquoter = strings.NewReplacer( - `\\`, `\`, - `\&`, `&`, - `\<`, `<`, - `\>`, `>`, - "\\`", "`", - `\'`, `'`, - `\"`, `"`, - `\{`, `{`, - `\}`, `}`, - `\$`, `$`, - `\#`, `#`, - `\|`, `|`, - `\?`, `?`, - `\(`, `(`, - `\)`, `)`, - `\;`, `;`, - `\ `, ` `, - `\[`, `[`, - `\]`, `]`, - `\*`, `*`, - `\~`, `~`, - ) + var unquoter = strings.NewReplacer( + `\\`, `\`, + `\&`, `&`, + `\<`, `<`, + `\>`, `>`, + "\\`", "`", + `\'`, `'`, + `\"`, `"`, + `\{`, `{`, + `\}`, `}`, + `\$`, `$`, + `\#`, `#`, + `\|`, `|`, + `\?`, `?`, + `\(`, `(`, + `\)`, `)`, + `\;`, `;`, + `\ `, ` `, + `\[`, `[`, + `\]`, `]`, + `\*`, `*`, + `\~`, `~`, + ) - for _, line := range lines[:len(lines)-1] { - line = unquoter.Replace(line) - if splitted := strings.SplitN(line, " -- ", 2); len(splitted) == 1 { - vals = append(vals, stripansi.Strip(splitted[0]), "") - } else { - vals = append(vals, stripansi.Strip(splitted[0]), splitted[1]) + for _, line := range lines[:len(lines)-1] { + line = unquoter.Replace(line) + if splitted := strings.SplitN(line, " -- ", 2); len(splitted) == 1 { + vals = append(vals, stripansi.Strip(splitted[0]), "") + } else { + vals = append(vals, stripansi.Strip(splitted[0]), splitted[1]) + } } - } - return carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) - }).Invoke(c).ToA().NoSpace([]rune("/=@:.,")...) + return carapace.ActionValuesDescribed(vals...).StyleF(style.ForPath) + }).Invoke(c).ToA().NoSpace([]rune("/=@:.,")...) + }) }) }