Skip to content

Commit

Permalink
feat: add zsh completions (#10040)
Browse files Browse the repository at this point in the history
Co-authored-by: Henrique Dias <hacdias@gmail.com>
  • Loading branch information
AmirMohammadFakhimi and hacdias committed Aug 17, 2023
1 parent cc79eeb commit ced3483
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/gotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
go-version: 1.19.x
- name: Check out Kubo
uses: actions/checkout@v3
- name: Install missing tools
run: sudo apt update && sudo apt install -y zsh
- name: Restore Go cache
uses: protocol/cache-go-action@v1
with:
Expand Down
29 changes: 28 additions & 1 deletion core/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"sort"
"strings"

"github.com/ipfs/go-ipfs-cmds"
cmds "github.com/ipfs/go-ipfs-cmds"
)

type commandEncoder struct {
Expand Down Expand Up @@ -169,6 +169,33 @@ To install the completions permanently, they can be moved to
return res.Emit(&buf)
},
},
"zsh": {
Helptext: cmds.HelpText{
Tagline: "Generate zsh shell completions.",
ShortDescription: "Generates command completions for the zsh shell.",
LongDescription: `
Generates command completions for the zsh shell.
The simplest way to see it working is write the completions
to a file and then source it:
> ipfs commands completion zsh > ipfs-completion.zsh
> source ./ipfs-completion.zsh
To install the completions permanently, they can be moved to
/etc/zsh/completions or sourced from your ~/.zshrc file.
`,
},
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var buf bytes.Buffer
if err := writeZshCompletions(root, &buf); err != nil {
return err
}
res.SetLength(uint64(buf.Len()))
return res.Emit(&buf)
},
},
"fish": {
Helptext: cmds.HelpText{
Tagline: "Generate fish shell completions.",
Expand Down
2 changes: 2 additions & 0 deletions core/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestROCommands(t *testing.T) {
"/commands/completion",
"/commands/completion/bash",
"/commands/completion/fish",
"/commands/completion/zsh",
"/dag",
"/dag/get",
"/dag/resolve",
Expand Down Expand Up @@ -102,6 +103,7 @@ func TestCommands(t *testing.T) {
"/commands/completion",
"/commands/completion/bash",
"/commands/completion/fish",
"/commands/completion/zsh",
"/config",
"/config/edit",
"/config/profile",
Expand Down
29 changes: 28 additions & 1 deletion core/commands/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func commandToCompletions(name string, fullName string, cmd *cmds.Command) *comp
return parsed
}

var bashCompletionTemplate, fishCompletionTemplate *template.Template
var bashCompletionTemplate, fishCompletionTemplate, zshCompletionTemplate *template.Template

func init() {
commandTemplate := template.Must(template.New("command").Parse(`
Expand Down Expand Up @@ -153,6 +153,28 @@ _ipfs() {
{{ template "command" . }}
}
complete -o nosort -o nospace -o default -F _ipfs ipfs
`))

zshCompletionTemplate = template.Must(commandTemplate.New("root").Parse(`#!bin/zsh
autoload bashcompinit
bashcompinit
_ipfs_compgen() {
local oldifs="$IFS"
IFS=$'\n'
while read -r line; do
COMPREPLY+=("$line")
done < <(compgen "$@")
IFS="$oldifs"
}
_ipfs() {
COMPREPLY=()
local index=1
local argidx=0
local word="${COMP_WORDS[COMP_CWORD]}"
{{ template "command" . }}
}
complete -o nosort -o nospace -o default -F _ipfs ipfs
`))

fishCommandTemplate := template.Must(template.New("command").Parse(`
Expand Down Expand Up @@ -221,3 +243,8 @@ func writeFishCompletions(cmd *cmds.Command, out io.Writer) error {
cmds := commandToCompletions("ipfs", "", cmd)
return fishCompletionTemplate.Execute(out, cmds)
}

func writeZshCompletions(cmd *cmds.Command, out io.Writer) error {
cmds := commandToCompletions("ipfs", "", cmd)
return zshCompletionTemplate.Execute(out, cmds)
}
13 changes: 13 additions & 0 deletions docs/command-completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,16 @@ The simplest way to use the completions logic:

To install the completions permanently, they can be moved to
`/etc/fish/completions` or `~/.config/fish/completions` or sourced from your `~/.config/fish/config.fish` file.

## ZSH

The zsh shell is also supported:

The simplest way to "eval" the completions logic:

```bash
> eval "$(ipfs commands completion zsh)"
```

To install the completions permanently, they can be moved to
`/etc/bash_completion.d` or sourced from your `~/.zshrc` file.
26 changes: 26 additions & 0 deletions test/cli/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,29 @@ func TestBashCompletion(t *testing.T) {
assert.NoError(t, res.Err)
})
}

func TestZshCompletion(t *testing.T) {
t.Parallel()
h := harness.NewT(t)
node := h.NewNode()

res := node.IPFS("commands", "completion", "zsh")

length := len(res.Stdout.String())
if length < 100 {
t.Fatalf("expected a long Bash completion file, but got one of length %d", length)
}

t.Run("completion file can be loaded in bash", func(t *testing.T) {
RequiresLinux(t)

completionFile := h.WriteToTemp(res.Stdout.String())
res = h.Runner.Run(harness.RunRequest{
Path: "zsh",
Args: []string{"-c", fmt.Sprintf("autoload -Uz compinit && compinit && source %s && echo -E $_comps[ipfs]", completionFile)},
})

assert.NoError(t, res.Err)
assert.NotEmpty(t, res.Stdout.String())
})
}

0 comments on commit ced3483

Please sign in to comment.