Skip to content

Commit 9b8fbfc

Browse files
Native zsh completion (#653)
1 parent 67b37a9 commit 9b8fbfc

9 files changed

Lines changed: 622 additions & 42 deletions

File tree

‎.github/workflows/completion.yml‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Shell completion tests
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
paths:
7+
- 'contrib/completion/**'
8+
- 'complete.go'
9+
- 'completion_shell_test.go'
10+
- 'commands.go'
11+
- 'commands_generate.go'
12+
13+
pull_request:
14+
types: [opened, synchronize, reopened]
15+
paths:
16+
- 'contrib/completion/**'
17+
- 'complete.go'
18+
- 'completion_shell_test.go'
19+
- 'commands.go'
20+
- 'commands_generate.go'
21+
22+
jobs:
23+
completion:
24+
name: bash / zsh / fish completion
25+
runs-on: ubuntu-26.04
26+
27+
steps:
28+
- name: Check out code into the Go module directory
29+
uses: actions/checkout@v6
30+
31+
- name: Dotenv Action
32+
id: dotenv
33+
uses: falti/dotenv-action@v1
34+
with:
35+
path: .github/workflows/env
36+
log-variables: true
37+
38+
- name: Set up Go ${{ steps.dotenv.outputs.go_version }}
39+
uses: actions/setup-go@v6
40+
with:
41+
go-version: ${{ steps.dotenv.outputs.go_version }}
42+
check-latest: true
43+
cache: true
44+
45+
# bash 5.x is already present on ubuntu-latest; bash-completion provides the
46+
# framework restic's bash completion relies on.
47+
- name: Install shells and restic
48+
run: sudo apt-get update && sudo apt-get install -y zsh fish bash-completion restic
49+
50+
- name: Run completion tests
51+
run: make test-completion

‎Makefile‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ test-race: $(GOBIN)/gotestsum prepare_test test-helpers ## Run unit tests with r
213213
@echo "[*] $@"
214214
@$(GOBIN)/gotestsum -- -short -race -count=1 $(TESTS)
215215

216+
test-completion: test-helpers ## Run shell completion integration tests (needs bash 4+, zsh, fish and restic; each is skipped when absent)
217+
@echo "[*] $@"
218+
@$(GOTEST) -count=1 -v -run 'ShellCompletion' ./
219+
216220
test-ci: export TEST_HELPERS=$(BUILD)
217221
test-ci: $(GOBIN)/gotestsum prepare_test test-helpers ## Run unit tests with coverage (for CI)
218222
@echo "[*] $@"

‎commands.go‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"fmt"
99
"io"
1010
"maps"
11+
"os"
12+
"path/filepath"
1113
"regexp"
1214
"slices"
1315
"strconv"
@@ -199,6 +201,8 @@ func completeCommand(ctx commandContext) error {
199201
requester := "unknown"
200202
requesterVersion := 0
201203

204+
debugCompletion(fmt.Sprintf("args: `%s`", strings.Join(args, "`, `")))
205+
202206
// Parse requester as first argument. Format "[kind]:v[version]", e.g. "bash:v1"
203207
if len(args) > 0 {
204208
matcher := regexp.MustCompile(`^(bash|zsh|fish):v(\d+)$`)
@@ -217,7 +221,7 @@ func completeCommand(ctx commandContext) error {
217221
}
218222

219223
// Ensure newer completion scripts will not fail on outdated resticprofile
220-
if requester == "zsh" || requesterVersion > 9 {
224+
if requesterVersion > 9 {
221225
return nil
222226
}
223227

@@ -228,10 +232,22 @@ func completeCommand(ctx commandContext) error {
228232
for _, completion := range completions {
229233
ctx.terminal.Println(completion)
230234
}
235+
debugCompletion(fmt.Sprintf("completions: `%s`", strings.Join(completions, "`, `")))
231236
}
232237
return nil
233238
}
234239

240+
func debugCompletion(line string) {
241+
debugFile := filepath.Clean(os.Getenv("RESTICPROFILE_DEBUG_COMPLETION"))
242+
if debugFile != "" && debugFile != "." {
243+
f, err := os.OpenFile(debugFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
244+
if err == nil {
245+
defer f.Close()
246+
fmt.Fprintln(f, line)
247+
}
248+
}
249+
}
250+
235251
func showProfileOrGroup(ctx commandContext) error {
236252
c := ctx.config
237253
flags := ctx.flags

‎commands_generate.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const pathTemplates = "contrib/templates"
2323
//go:embed contrib/completion/bash-completion.sh
2424
var bashCompletionScript string
2525

26-
//go:embed contrib/completion/zsh-completion.sh
26+
//go:embed contrib/completion/zsh-completion.zsh
2727
var zshCompletionScript string
2828

2929
//go:embed contrib/completion/fish-completion.fish

‎commands_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func TestCompleteCall(t *testing.T) {
196196
{args: []string{"bash:v1", "--"}, expected: expectedFlags},
197197
{args: []string{"fish:v1", "--"}, expected: expectedFlagsWithDescriptions},
198198
{args: []string{"bash:v10", "--"}, expected: ""},
199-
{args: []string{"zsh:v1", "--"}, expected: ""},
199+
{args: []string{"zsh:v1", "--"}, expected: expectedFlags},
200200
}
201201

202202
for _, test := range testTable {
@@ -238,7 +238,7 @@ func TestGenerateCommand(t *testing.T) {
238238
buffer.Reset()
239239
assert.Nil(t, generateCommand(contextWithArguments([]string{"--zsh-completion"})))
240240
assert.Equal(t, strings.TrimSpace(zshCompletionScript), strings.TrimSpace(buffer.String()))
241-
assert.Contains(t, zshCompletionScript, "#!/usr/bin/env zsh")
241+
assert.Contains(t, zshCompletionScript, "#compdef resticprofile")
242242
})
243243

244244
t.Run("--fish-completion", func(t *testing.T) {

0 commit comments

Comments
 (0)