Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add zsh completions #10040

Merged
merged 5 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
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)

Check warning on line 196 in core/commands/commands.go

View check run for this annotation

Codecov / codecov/patch

core/commands/commands.go#L190-L196

Added lines #L190 - L196 were not covered by tests
},
},
"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 @@
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 @@
{{ 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 @@
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)

Check warning on line 249 in core/commands/completion.go

View check run for this annotation

Codecov / codecov/patch

core/commands/completion.go#L247-L249

Added lines #L247 - L249 were not covered by tests
}
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())
})
}
Loading