feat: Add command line completion for fish

This commit is contained in:
Andreas Källberg 2022-10-23 03:31:47 +02:00 committed by Jorropo
parent a9cbdef023
commit 2fd4e197a0
6 changed files with 146 additions and 6 deletions

View File

@ -160,7 +160,7 @@ jobs:
tar xfz go1.19.1.linux-amd64.tar.gz
echo "export PATH=$(pwd)/go/bin:\$PATH" >> ~/.bashrc
- run: go version
- run: sudo apt install socat net-tools
- run: sudo apt install socat net-tools fish
- checkout
- run:

View File

@ -169,6 +169,33 @@ To install the completions permanently, they can be moved to
return res.Emit(&buf)
},
},
"fish": {
Helptext: cmds.HelpText{
Tagline: "Generate fish shell completions.",
ShortDescription: "Generates command completions for the fish shell.",
LongDescription: `
Generates command completions for the fish shell.
The simplest way to see it working is write the completions
to a file and then source it:
> ipfs commands completion fish > ipfs-completion.fish
> source ./ipfs-completion.fish
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.
`,
},
NoRemote: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var buf bytes.Buffer
if err := writeFishCompletions(root, &buf); err != nil {
return err
}
res.SetLength(uint64(buf.Len()))
return res.Emit(&buf)
},
},
},
}
}

View File

@ -24,6 +24,7 @@ func TestROCommands(t *testing.T) {
"/commands",
"/commands/completion",
"/commands/completion/bash",
"/commands/completion/fish",
"/dag",
"/dag/get",
"/dag/resolve",
@ -99,6 +100,7 @@ func TestCommands(t *testing.T) {
"/commands",
"/commands/completion",
"/commands/completion/bash",
"/commands/completion/fish",
"/config",
"/config/edit",
"/config/profile",

View File

@ -10,41 +10,62 @@ import (
type completionCommand struct {
Name string
FullName string
Description string
Subcommands []*completionCommand
Flags []*singleOption
Options []*singleOption
ShortFlags []string
ShortOptions []string
LongFlags []string
LongOptions []string
IsFinal bool
}
func commandToCompletions(name string, cmd *cmds.Command) *completionCommand {
type singleOption struct {
LongNames []string
ShortNames []string
Description string
}
func commandToCompletions(name string, fullName string, cmd *cmds.Command) *completionCommand {
parsed := &completionCommand{
Name: name,
Name: name,
FullName: fullName,
Description: cmd.Helptext.Tagline,
IsFinal: len(cmd.Subcommands) == 0,
}
for name, subCmd := range cmd.Subcommands {
parsed.Subcommands = append(parsed.Subcommands, commandToCompletions(name, subCmd))
parsed.Subcommands = append(parsed.Subcommands,
commandToCompletions(name, fullName+" "+name, subCmd))
}
sort.Slice(parsed.Subcommands, func(i, j int) bool {
return parsed.Subcommands[i].Name < parsed.Subcommands[j].Name
})
for _, opt := range cmd.Options {
flag := &singleOption{Description: opt.Description()}
flag.LongNames = append(flag.LongNames, opt.Name())
if opt.Type() == cmds.Bool {
parsed.LongFlags = append(parsed.LongFlags, opt.Name())
for _, name := range opt.Names() {
if len(name) == 1 {
parsed.ShortFlags = append(parsed.ShortFlags, name)
flag.ShortNames = append(flag.ShortNames, name)
break
}
}
parsed.Flags = append(parsed.Flags, flag)
} else {
parsed.LongOptions = append(parsed.LongOptions, opt.Name())
for _, name := range opt.Names() {
if len(name) == 1 {
parsed.ShortOptions = append(parsed.ShortOptions, name)
flag.ShortNames = append(flag.ShortNames, name)
break
}
}
parsed.Options = append(parsed.Options, flag)
}
}
sort.Slice(parsed.LongFlags, func(i, j int) bool {
@ -62,7 +83,7 @@ func commandToCompletions(name string, cmd *cmds.Command) *completionCommand {
return parsed
}
var bashCompletionTemplate *template.Template
var bashCompletionTemplate, fishCompletionTemplate *template.Template
func init() {
commandTemplate := template.Must(template.New("command").Parse(`
@ -133,10 +154,71 @@ _ipfs() {
}
complete -o nosort -o nospace -o default -F _ipfs ipfs
`))
fishCommandTemplate := template.Must(template.New("command").Parse(`
{{- if .IsFinal -}}
complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ .FullName }}' -F
{{ end -}}
{{- range .Flags -}}
complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ $.FullName }}' {{ range .ShortNames }}-s {{.}} {{end}}{{ range .LongNames }}-l {{.}} {{end}}-d "{{ .Description }}"
{{ end -}}
{{- range .Options -}}
complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ $.FullName }}' -r {{ range .ShortNames }}-s {{.}} {{end}}{{ range .LongNames }}-l {{.}} {{end}}-d "{{ .Description }}"
{{ end -}}
{{- range .Subcommands }}
#{{ .FullName }}
complete -c ipfs -n '__fish_ipfs_use_subcommand{{ .FullName }}' -a {{ .Name }} -d "{{ .Description }}"
{{ template "command" . }}
{{ end -}}
`))
fishCompletionTemplate = template.Must(fishCommandTemplate.New("root").Parse(`#!/usr/bin/env fish
function __fish_ipfs_seen_all_subcommands_from
set -l cmd (commandline -poc)
set -e cmd[1]
for c in $argv
if not contains -- $c $cmd
return 1
end
end
return 0
end
function __fish_ipfs_use_subcommand
set -e argv[-1]
set -l cmd (commandline -poc)
set -e cmd[1]
for i in $cmd
switch $i
case '-*'
continue
case $argv[1]
set argv $argv[2..]
continue
case '*'
return 1
end
end
test -z "$argv"
end
complete -c ipfs -l help -d "Show the full command help text."
complete -c ipfs --keep-order --no-files
{{ template "command" . }}
`))
}
// writeBashCompletions generates a bash completion script for the given command tree.
func writeBashCompletions(cmd *cmds.Command, out io.Writer) error {
cmds := commandToCompletions("ipfs", cmd)
cmds := commandToCompletions("ipfs", "", cmd)
return bashCompletionTemplate.Execute(out, cmds)
}
// writeFishCompletions generates a fish completion script for the given command tree.
func writeFishCompletions(cmd *cmds.Command, out io.Writer) error {
cmds := commandToCompletions("ipfs", "", cmd)
return fishCompletionTemplate.Execute(out, cmds)
}

View File

@ -11,3 +11,16 @@ The simplest way to "eval" the completions logic:
To install the completions permanently, they can be moved to
`/etc/bash_completion.d` or sourced from your `~/.bashrc` file.
## Fish
The fish shell is also supported:
The simplest way to use the completions logic:
```bash
> ipfs commands completion fish | source
```
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.

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
test_description="Test generated fish completions"
. lib/test-lib.sh
test_expect_success "'ipfs commands completion fish' succeeds" '
ipfs commands completion fish > completions.fish
'
test_expect_success "generated completions completes 'ipfs version'" '
fish -c "source completions.fish && complete -C \"ipfs ver\" | grep -q \"version.Show IPFS version information.\" "
'
test_done