feat: add zsh completions (#10040)
Co-authored-by: Henrique Dias <hacdias@gmail.com>
Amir Mohammad Fakhimi committed
Aug 17, 2023 at 18:13 UTC
ced348366c13c841c93ec5734732e5789cba4cbb
6 files changed
+99
-2
.github/workflows/gotest.yml
+2
@@ -35,6 +35,8 @@ jobs:
35
go-version: 1.19.x
36
- name: Check out Kubo
37
uses: actions/checkout@v3
38
+ - name: Install missing tools
39
+ run: sudo apt update && sudo apt install -y zsh
40
- name: Restore Go cache
41
uses: protocol/cache-go-action@v1
42
with:
core/commands/commands.go
+28
-1
@@ -13,7 +13,7 @@ import (
13
"sort"
14
"strings"
15
16
- "github.com/ipfs/go-ipfs-cmds"
16
+ cmds "github.com/ipfs/go-ipfs-cmds"
17
)
18
19
type commandEncoder struct {
@@ -169,6 +169,33 @@ To install the completions permanently, they can be moved to
169
return res.Emit(&buf)
170
},
171
},
172
+ "zsh": {
173
+ Helptext: cmds.HelpText{
174
+ Tagline: "Generate zsh shell completions.",
175
+ ShortDescription: "Generates command completions for the zsh shell.",
176
+ LongDescription: `
177
+Generates command completions for the zsh shell.
178
+
179
+The simplest way to see it working is write the completions
180
+to a file and then source it:
181
+
182
+ > ipfs commands completion zsh > ipfs-completion.zsh
183
+ > source ./ipfs-completion.zsh
184
+
185
+To install the completions permanently, they can be moved to
186
+/etc/zsh/completions or sourced from your ~/.zshrc file.
187
+`,
188
+ },
189
+ NoRemote: true,
190
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
191
+ var buf bytes.Buffer
192
+ if err := writeZshCompletions(root, &buf); err != nil {
193
+ return err
194
+ }
195
+ res.SetLength(uint64(buf.Len()))
196
+ return res.Emit(&buf)
197
+ },
198
+ },
199
"fish": {
200
Helptext: cmds.HelpText{
201
Tagline: "Generate fish shell completions.",
core/commands/commands_test.go
+2
@@ -25,6 +25,7 @@ func TestROCommands(t *testing.T) {
25
"/commands/completion",
26
"/commands/completion/bash",
27
"/commands/completion/fish",
28
+ "/commands/completion/zsh",
29
"/dag",
30
"/dag/get",
31
"/dag/resolve",
@@ -102,6 +103,7 @@ func TestCommands(t *testing.T) {
103
"/commands/completion",
104
"/commands/completion/bash",
105
"/commands/completion/fish",
106
+ "/commands/completion/zsh",
107
"/config",
108
"/config/edit",
109
"/config/profile",
core/commands/completion.go
+28
-1
@@ -83,7 +83,7 @@ func commandToCompletions(name string, fullName string, cmd *cmds.Command) *comp
83
return parsed
84
}
85
86
-var bashCompletionTemplate, fishCompletionTemplate *template.Template
86
+var bashCompletionTemplate, fishCompletionTemplate, zshCompletionTemplate *template.Template
87
88
func init() {
89
commandTemplate := template.Must(template.New("command").Parse(`
@@ -153,6 +153,28 @@ _ipfs() {
153
{{ template "command" . }}
154
}
155
complete -o nosort -o nospace -o default -F _ipfs ipfs
156
+`))
157
+
158
+ zshCompletionTemplate = template.Must(commandTemplate.New("root").Parse(`#!bin/zsh
159
+autoload bashcompinit
160
+bashcompinit
161
+_ipfs_compgen() {
162
+local oldifs="$IFS"
163
+IFS=$'\n'
164
+while read -r line; do
165
+ COMPREPLY+=("$line")
166
+done < <(compgen "$@")
167
+IFS="$oldifs"
168
+}
169
+
170
+_ipfs() {
171
+COMPREPLY=()
172
+local index=1
173
+local argidx=0
174
+local word="${COMP_WORDS[COMP_CWORD]}"
175
+{{ template "command" . }}
176
+}
177
+complete -o nosort -o nospace -o default -F _ipfs ipfs
178
`))
179
180
fishCommandTemplate := template.Must(template.New("command").Parse(`
@@ -221,3 +243,8 @@ func writeFishCompletions(cmd *cmds.Command, out io.Writer) error {
243
cmds := commandToCompletions("ipfs", "", cmd)
244
return fishCompletionTemplate.Execute(out, cmds)
245
}
246
+
247
+func writeZshCompletions(cmd *cmds.Command, out io.Writer) error {
248
+ cmds := commandToCompletions("ipfs", "", cmd)
249
+ return zshCompletionTemplate.Execute(out, cmds)
250
+}
docs/command-completion.md
+13
@@ -24,3 +24,16 @@ The simplest way to use the completions logic:
24
25
To install the completions permanently, they can be moved to
26
`/etc/fish/completions` or `~/.config/fish/completions` or sourced from your `~/.config/fish/config.fish` file.
27
+
28
+## ZSH
29
+
30
+The zsh shell is also supported:
31
+
32
+The simplest way to "eval" the completions logic:
33
+
34
+```bash
35
+> eval "$(ipfs commands completion zsh)"
36
+```
37
+
38
+To install the completions permanently, they can be moved to
39
+`/etc/bash_completion.d` or sourced from your `~/.zshrc` file.
test/cli/completion_test.go
+26
@@ -29,3 +29,29 @@ func TestBashCompletion(t *testing.T) {
29
assert.NoError(t, res.Err)
30
})
31
}
32
+
33
+func TestZshCompletion(t *testing.T) {
34
+ t.Parallel()
35
+ h := harness.NewT(t)
36
+ node := h.NewNode()
37
+
38
+ res := node.IPFS("commands", "completion", "zsh")
39
+
40
+ length := len(res.Stdout.String())
41
+ if length < 100 {
42
+ t.Fatalf("expected a long Bash completion file, but got one of length %d", length)
43
+ }
44
+
45
+ t.Run("completion file can be loaded in bash", func(t *testing.T) {
46
+ RequiresLinux(t)
47
+
48
+ completionFile := h.WriteToTemp(res.Stdout.String())
49
+ res = h.Runner.Run(harness.RunRequest{
50
+ Path: "zsh",
51
+ Args: []string{"-c", fmt.Sprintf("autoload -Uz compinit && compinit && source %s && echo -E $_comps[ipfs]", completionFile)},
52
+ })
53
+
54
+ assert.NoError(t, res.Err)
55
+ assert.NotEmpty(t, res.Stdout.String())
56
+ })
57
+}