Skip to content
Open
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
17 changes: 9 additions & 8 deletions fish.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"strings"
"unicode/utf8"
)

// ToFishCompletion creates a fish completion string for the `*Command`
Expand Down Expand Up @@ -125,15 +126,15 @@ func prepareFishFlags(binary string, owner *Command) []string {

fishAddFileFlag(f, completion)

for idx, opt := range f.Names() {
if idx == 0 {
fmt.Fprintf(completion,
" -l %s", strings.TrimSpace(opt),
)
for _, opt := range f.Names() {
opt = strings.TrimSpace(opt)
// fish's -s takes a single character and a longer string is read as
// a group of one-character options, so only a one-character name
// is a short option. Any other name is a long option.
if utf8.RuneCountInString(opt) == 1 {
fmt.Fprintf(completion, " -s %s", opt)
} else {
fmt.Fprintf(completion,
" -s %s", strings.TrimSpace(opt),
)
fmt.Fprintf(completion, " -l %s", opt)
}
}

Expand Down
24 changes: 24 additions & 0 deletions fish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ func TestFishCompletion(t *testing.T) {
expectFileContent(t, "testdata/expected-fish-full.fish", res)
}

func TestFishCompletionShortAndLongNames(t *testing.T) {
// fish reads the argument of -s as a group of one-character options, so a
// name that is longer than one character must be given with -l, and a
// one-character name must be given with -s, wherever it appears in the
// flag's list of names.
cmd := &Command{
Name: "greet",
Flags: []Flag{
&BoolFlag{Name: "verbose", Aliases: []string{"loud", "v"}},
&StringFlag{Name: "o", Aliases: []string{"output", "out"}},
},
}
cmd.setupCommandGraph()

res, err := cmd.ToFishCompletion()
require.NoError(t, err)

assert.Contains(t, res, "complete -c greet -n '__fish_greet_no_subcommand' -f -l verbose -l loud -s v")
assert.Contains(t, res, "complete -c greet -n '__fish_greet_no_subcommand' -f -s o -l output -l out -r")
assert.NotContains(t, res, " -s loud")
assert.NotContains(t, res, " -s output")
assert.NotContains(t, res, " -l o ")
}

func TestFishCompletionBackslashEscaping(t *testing.T) {
// Inside fish single-quoted strings the only escape sequences are \\ and
// \', so a backslash in a description must be emitted as \\. An unescaped
Expand Down
8 changes: 4 additions & 4 deletions testdata/expected-fish-full.fish
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ function __fish_greet_no_subcommand --description 'Test if there has been any su
end

complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text'
complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r
complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -l fl -s f -r
complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_greet_no_subcommand' -l logfile -r
complete -c greet -n '__fish_greet_no_subcommand' -l foofile -r
complete -x -c greet -n '__fish_greet_no_subcommand' -a 'config' -d 'another usage test'
complete -c greet -n '__fish_seen_subcommand_from config c' -l flag -s fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from config c' -l flag -l fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help'
complete -x -c greet -n '__fish_seen_subcommand_from config c; and not __fish_seen_subcommand_from sub-config s ss help h' -a 'sub-config' -d 'another usage test'
complete -c greet -n '__fish_seen_subcommand_from config c; and __fish_seen_subcommand_from sub-config s ss' -f -l sub-flag -s sub-fl -s s -r
complete -c greet -n '__fish_seen_subcommand_from config c; and __fish_seen_subcommand_from sub-config s ss' -f -l sub-flag -l sub-fl -s s -r
complete -c greet -n '__fish_seen_subcommand_from config c; and __fish_seen_subcommand_from sub-config s ss' -f -l sub-command-flag -s s -d 'some usage text'
complete -c greet -n '__fish_seen_subcommand_from config c; and __fish_seen_subcommand_from sub-config s ss' -f -l help -s h -d 'show help'
complete -x -c greet -n '__fish_seen_subcommand_from config c; and __fish_seen_subcommand_from sub-config s ss; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command'
Expand All @@ -34,7 +34,7 @@ complete -c greet -n '__fish_seen_subcommand_from hidden-command' -f -l completa
complete -c greet -n '__fish_seen_subcommand_from hidden-command' -f -l help -s h -d 'show help'
complete -x -c greet -n '__fish_seen_subcommand_from hidden-command; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command'
complete -x -c greet -n '__fish_greet_no_subcommand' -a 'usage' -d 'standard usage text'
complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -s fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from usage u' -l flag -l fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help'
complete -x -c greet -n '__fish_seen_subcommand_from usage u; and not __fish_seen_subcommand_from sub-usage su help h' -a 'sub-usage' -d 'standard usage text'
Expand Down
Loading