From f1b8fe152766d633183d3d8a7b23d9da41bc729b Mon Sep 17 00:00:00 2001 From: Bittrance Date: Mon, 28 Apr 2025 18:18:36 +0200 Subject: [PATCH 1/3] Simpler top-level context detection for fish completions. Previously, completions checked against all levels of subcommands. This change has it look only for top-level commands, which must necessarily be present before their children. It also adds hidden top-level commands to the list. --- fish.go | 21 ++++++++++++--------- testdata/expected-fish-full.fish | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/fish.go b/fish.go index b1086ebc9e..4407fceb9b 100644 --- a/fish.go +++ b/fish.go @@ -30,16 +30,15 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { if err != nil { return err } - allCommands := []string{} // Add global flags - completions := cmd.prepareFishFlags(cmd.VisibleFlags(), allCommands) + completions := cmd.prepareFishFlags(cmd.VisibleFlags(), []string{}) // Add help flag if !cmd.HideHelp { completions = append( completions, - cmd.prepareFishFlags([]Flag{HelpFlag}, allCommands)..., + cmd.prepareFishFlags([]Flag{HelpFlag}, []string{})..., ) } @@ -47,24 +46,29 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { if !cmd.HideVersion { completions = append( completions, - cmd.prepareFishFlags([]Flag{VersionFlag}, allCommands)..., + cmd.prepareFishFlags([]Flag{VersionFlag}, []string{})..., ) } // Add commands and their flags completions = append( completions, - cmd.prepareFishCommands(cmd.VisibleCommands(), &allCommands, []string{})..., + cmd.prepareFishCommands(cmd.VisibleCommands(), []string{})..., ) + toplevelCommandNames := []string{} + for _, child := range cmd.Commands { + toplevelCommandNames = append(toplevelCommandNames, child.Names()...) + } + return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{ Command: cmd, Completions: completions, - AllCommands: allCommands, + AllCommands: toplevelCommandNames, }) } -func (cmd *Command) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string { +func (cmd *Command) prepareFishCommands(commands []*Command, previousCommands []string) []string { completions := []string{} for _, command := range commands { var completion strings.Builder @@ -88,7 +92,6 @@ func (cmd *Command) prepareFishCommands(commands []*Command, allCommands *[]stri ) } - *allCommands = append(*allCommands, command.Names()...) completions = append(completions, completion.String()) completions = append( completions, @@ -100,7 +103,7 @@ func (cmd *Command) prepareFishCommands(commands []*Command, allCommands *[]stri completions = append( completions, cmd.prepareFishCommands( - command.Commands, allCommands, command.Names(), + command.Commands, command.Names(), )..., ) } diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index f586c66fda..25329b4636 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -2,7 +2,7 @@ function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet' for i in (commandline -opc) - if contains -- $i config c sub-config s ss info i in some-command usage u sub-usage su + if contains -- $i config c info i in some-command hidden-command usage u return 1 end end From 55db2a0b66d452738e508415083263196b792ce2 Mon Sep 17 00:00:00 2001 From: Bittrance Date: Mon, 28 Apr 2025 21:50:54 +0200 Subject: [PATCH 2/3] Fish completions need not add their own help commands. --- fish.go | 24 ------------------------ testdata/expected-fish-full.fish | 8 -------- 2 files changed, 32 deletions(-) diff --git a/fish.go b/fish.go index 4407fceb9b..8d7af2797a 100644 --- a/fish.go +++ b/fish.go @@ -34,22 +34,6 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { // Add global flags completions := cmd.prepareFishFlags(cmd.VisibleFlags(), []string{}) - // Add help flag - if !cmd.HideHelp { - completions = append( - completions, - cmd.prepareFishFlags([]Flag{HelpFlag}, []string{})..., - ) - } - - // Add version flag - if !cmd.HideVersion { - completions = append( - completions, - cmd.prepareFishFlags([]Flag{VersionFlag}, []string{})..., - ) - } - // Add commands and their flags completions = append( completions, @@ -84,14 +68,6 @@ func (cmd *Command) prepareFishCommands(commands []*Command, previousCommands [] " -d '%s'", escapeSingleQuotes(command.Usage)) } - - if !command.HideHelp { - completions = append( - completions, - cmd.prepareFishFlags([]Flag{HelpFlag}, command.Names())..., - ) - } - completions = append(completions, completion.String()) completions = append( completions, diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index 25329b4636..42aee6d188 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -14,24 +14,16 @@ 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 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 -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help' -complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version' -complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'config c' -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' -f -l another-flag -s b -d 'another usage text' -complete -c greet -n '__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 not __fish_seen_subcommand_from sub-config s ss' -a 'sub-config s ss' -d 'another usage test' complete -c greet -n '__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 sub-config s ss' -f -l sub-command-flag -s s -d 'some usage text' -complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d 'show help' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' -complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'some-command' -complete -c greet -n '__fish_seen_subcommand_from usage u' -f -l help -s h -d 'show help' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -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' -f -l another-flag -s b -d 'another usage text' -complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -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' -a 'sub-usage su' -d 'standard usage text' complete -c greet -n '__fish_seen_subcommand_from sub-usage su' -f -l sub-command-flag -s s -d 'some usage text' From 66d871f8b5bf2b55bcbf72f70b81eba421eca602 Mon Sep 17 00:00:00 2001 From: Bittrance Date: Mon, 28 Apr 2025 22:38:16 +0200 Subject: [PATCH 3/3] Fish completions for commands and flags inside hidden command. --- command_test.go | 26 +++++++++++++++++++++++++- fish.go | 28 +++++++++++++++------------- testdata/expected-fish-full.fish | 1 + 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/command_test.go b/command_test.go index b44ed07932..f346294339 100644 --- a/command_test.go +++ b/command_test.go @@ -98,6 +98,11 @@ func buildExtendedTestCommand() *Command { }, { Name: "hidden-command", Hidden: true, + Flags: []Flag{ + &BoolFlag{ + Name: "completable", + }, + }, }, { Aliases: []string{"u"}, Flags: []Flag{ @@ -4876,7 +4881,26 @@ func TestJSONExportCommand(t *testing.T) { "defaultCommand": "", "category": "", "commands": null, - "flags": null, + "flags": [ + { + "name": "completable", + "category": "", + "defaultText": "", + "usage": "", + "required": false, + "hidden": false, + "hideDefault": false, + "local": false, + "defaultValue": false, + "aliases": null, + "takesFileArg": false, + "config": { + "Count": null + }, + "onlyOnce": false, + "validateDefaults": false + } + ], "hideHelp": false, "hideHelpCommand": false, "hideVersion": false, diff --git a/fish.go b/fish.go index 8d7af2797a..93d4d4aab1 100644 --- a/fish.go +++ b/fish.go @@ -37,7 +37,7 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { // Add commands and their flags completions = append( completions, - cmd.prepareFishCommands(cmd.VisibleCommands(), []string{})..., + cmd.prepareFishCommands(cmd.Commands, []string{})..., ) toplevelCommandNames := []string{} @@ -55,20 +55,22 @@ func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error { func (cmd *Command) prepareFishCommands(commands []*Command, previousCommands []string) []string { completions := []string{} for _, command := range commands { - var completion strings.Builder - fmt.Fprintf(&completion, - "complete -x -c %s -n '%s' -a '%s'", - cmd.Name, - cmd.fishSubcommandHelper(previousCommands, commands), - strings.Join(command.Names(), " "), - ) - - if command.Usage != "" { + if !command.Hidden { + var completion strings.Builder fmt.Fprintf(&completion, - " -d '%s'", - escapeSingleQuotes(command.Usage)) + "complete -x -c %s -n '%s' -a '%s'", + cmd.Name, + cmd.fishSubcommandHelper(previousCommands, commands), + strings.Join(command.Names(), " "), + ) + + if command.Usage != "" { + fmt.Fprintf(&completion, + " -d '%s'", + escapeSingleQuotes(command.Usage)) + } + completions = append(completions, completion.String()) } - completions = append(completions, completion.String()) completions = append( completions, cmd.prepareFishFlags(command.VisibleFlags(), command.Names())..., diff --git a/testdata/expected-fish-full.fish b/testdata/expected-fish-full.fish index 42aee6d188..eb29d69b0d 100644 --- a/testdata/expected-fish-full.fish +++ b/testdata/expected-fish-full.fish @@ -22,6 +22,7 @@ complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-fla complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-command-flag -s s -d 'some usage text' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information' complete -x -c greet -n '__fish_greet_no_subcommand' -a 'some-command' +complete -c greet -n '__fish_seen_subcommand_from hidden-command' -f -l completable complete -x -c greet -n '__fish_greet_no_subcommand' -a 'usage u' -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' -f -l another-flag -s b -d 'another usage text'