diff --git a/app/cmd/autocomplete.go b/app/cmd/autocomplete.go new file mode 100644 index 0000000..90e20d1 --- /dev/null +++ b/app/cmd/autocomplete.go @@ -0,0 +1,125 @@ +package cmd + +import ( + "fmt" + "io" + "net/http" + "os" + "os/exec" + + "github.com/adrg/xdg" + "github.com/urfave/cli/v2" +) + +// CmdAutocomplete manages autocompletion +var CmdAutocomplete = cli.Command{ + Name: "shellcompletion", + Aliases: []string{"autocomplete"}, + Category: catSetup, + Usage: "Install shell completion for ledo", + Description: "Install shell completion for ledo", + ArgsUsage: " (bash, zsh, powershell, fish)", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "install", + Usage: "Persist in shell config instead of printing commands", + }, + }, + Action: runAutocompleteAdd, +} + +func runAutocompleteAdd(ctx *cli.Context) error { + var remoteFile, localFile, cmds string + shell := ctx.Args().First() + + switch shell { + case "zsh": + remoteFile = "contrib/autocomplete.zsh" + localFile = "autocomplete.zsh" + cmds = "echo 'PROG=ledo _CLI_ZSH_AUTOCOMPLETE_HACK=1 source \"%s\"' >> ~/.zshrc && source ~/.zshrc" + + case "bash": + remoteFile = "contrib/autocomplete.sh" + localFile = "autocomplete.sh" + cmds = "echo 'PROG=ledo source \"%s\"' >> ~/.bashrc && source ~/.bashrc" + + case "powershell": + remoteFile = "contrib/autocomplete.ps1" + localFile = "tea.ps1" + cmds = "\"& %s\" >> $profile" + + case "fish": + return writeFishAutoCompleteFile(ctx) + + default: + return fmt.Errorf("Must specify valid %s", ctx.Command.ArgsUsage) + } + + localPath, err := xdg.ConfigFile("ledo/" + localFile) + if err != nil { + return err + } + + cmds = fmt.Sprintf(cmds, localPath) + if err = writeRemoteAutoCompleteFile(remoteFile, localPath); err != nil { + return err + } + + if ctx.Bool("install") { + fmt.Println("Installing in your shellrc") + installer := exec.Command(shell, "-c", cmds) + if shell == "powershell" { + installer = exec.Command("powershell.exe", "-Command", cmds) + } + out, err := installer.CombinedOutput() + if err != nil { + return fmt.Errorf("Couldn't run the commands: %s %s", err, out) + } + } else { + fmt.Println("\n# Run the following commands to install autocompletion (or use --install)") + fmt.Println(cmds) + } + + return nil +} + +func writeRemoteAutoCompleteFile(file, destPath string) error { + url := fmt.Sprintf("https://git.cynarski.pl/LeDo/ledo/raw/branch/master/%s", file) + fmt.Println("Fetching " + url) + + res, err := http.Get(url) + if err != nil { + return err + } + defer res.Body.Close() + + writer, err := os.Create(destPath) + if err != nil { + return err + } + defer writer.Close() + + _, err = io.Copy(writer, res.Body) + return err +} + +func writeFishAutoCompleteFile(ctx *cli.Context) error { + script, err := ctx.App.ToFishCompletion() + if err != nil { + return err + } + + localPath, err := xdg.ConfigFile("fish/conf.d/tea_completion.fish") + if err != nil { + return err + } + writer, err := os.Create(localPath) + if err != nil { + return err + } + if _, err = io.WriteString(writer, script); err != nil { + return err + } + fmt.Printf("Installed tab completion to %s\n", localPath) + return nil +} diff --git a/contrib/autocomplete.ps1 b/contrib/autocomplete.ps1 new file mode 100644 index 0000000..81812a6 --- /dev/null +++ b/contrib/autocomplete.ps1 @@ -0,0 +1,9 @@ +$fn = $($MyInvocation.MyCommand.Name) +$name = $fn -replace "(.*)\.ps1$", '$1' +Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { + param($commandName, $wordToComplete, $cursorPosition) + $other = "$wordToComplete --generate-bash-completion" + Invoke-Expression $other | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } \ No newline at end of file diff --git a/contrib/autocomplete.sh b/contrib/autocomplete.sh new file mode 100644 index 0000000..f0f6241 --- /dev/null +++ b/contrib/autocomplete.sh @@ -0,0 +1,21 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG +unset PROG diff --git a/contrib/autocomplete.zsh b/contrib/autocomplete.zsh new file mode 100644 index 0000000..cf39c88 --- /dev/null +++ b/contrib/autocomplete.zsh @@ -0,0 +1,23 @@ +#compdef $PROG + +_cli_zsh_autocomplete() { + + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + else + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + fi + + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi + + return +} + +compdef _cli_zsh_autocomplete $PROG diff --git a/go.mod b/go.mod index 86673b1..24f112e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.17 require ( github.com/AlecAivazis/survey/v2 v2.3.2 github.com/Masterminds/semver v1.5.0 + github.com/adrg/xdg v0.3.4 github.com/spf13/viper v1.8.1 github.com/thoas/go-funk v0.9.1 github.com/urfave/cli/v2 v2.3.0 diff --git a/go.sum b/go.sum index 2c018bb..628ed30 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/adrg/xdg v0.3.4 h1:0BivHfQ0LSGQrFTaEZ0hyQLm/HAidci7m+1cT6wKKdA= +github.com/adrg/xdg v0.3.4/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/main.go b/main.go index cebab90..aa2472c 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ func main() { app.Commands = []*cli.Command{ &cmd.CmdDocker, &cmd.CmdMode, + &cmd.CmdAutocomplete, } app.EnableBashCompletion = true err := app.Run(os.Args)