Repository: urfave/cli
Pull request #1903 (2024-05-01 16:41:26)

Include flag descriptions when using zsh completion
<!--
  This template provides some ideas of things to include in your PR description.
  To start, try providing a short summary of your changes in the Title above.
  If a section of the PR template does not apply to this PR, then delete that section.
 -->

## What type of PR is this?

<!--
  Delete any of the following that do not apply:
 -->

- feature

## What this PR does / why we need it:

This PR ensures that flags' `Usage` fields are included when using zsh completion (if their description is non-empty). This functionality is in line with how this is handled for command completion, see https://github.com/urfave/cli/blob/043b774d0f2c64d2ed4e22e87c2f6ec54bfcb30b/help.go#L163

### Without Changes
![without-desc](https://github.com/urfave/cli/assets/38653851/3e52d584-f706-4a81-9fc1-fd08361e7778)


### With changes 
![with-desc](https://github.com/urfave/cli/assets/38653851/ea487a85-f1db-43a2-ba6a-094b8ab49def)

## Testing

I tested these changes locally against a CLI tool that I am migrating to urfave/cli/v3. I also ran `make test` and encountered no errors, but then again I also did not add any test cases (would be open to doing so if this feature is desired).


## Release Notes

<!--
  If this PR makes user facing changes, please describe them here. This
  description will be copied into the release notes/changelog, whenever the
  next version is released. Keep this section short, and focus on high level
  changes.

  Put your text between the block. To omit notes, use NONE within the block.
-->

```release-note
Flag usage fields are now included when using zsh completion
```



Patch:
diff --git a/help.go b/help.go
index dc97000..82a4b76 100644
--- a/help.go
+++ b/help.go
@@ -192,6 +192,11 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
 			continue
 		}
 
+		usage := ""
+		if docFlag, ok := flag.(DocGenerationFlag); ok {
+			usage = docFlag.GetUsage()
+		}
+
 		name := strings.TrimSpace(flag.Names()[0])
 		// this will get total count utf8 letters in flag name
 		count := utf8.RuneCountInString(name)
@@ -206,8 +211,10 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
 		// match if last argument matches this flag and it is not repeated
 		if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) {
 			flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
+			if usage != "" && strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
+				flagCompletion = fmt.Sprintf("%s:%s", flagCompletion, usage)
+			}
 			fmt.Fprintln(writer, flagCompletion)
-
 		}
 	}
 }


Test patch:
diff --git a/help_test.go b/help_test.go
index db3d2eb..91612d1 100644
--- a/help_test.go
+++ b/help_test.go
@@ -1080,18 +1080,18 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 	origArgv := os.Args
 	t.Cleanup(func() { os.Args = origArgv })
 
-	t.Setenv("SHELL", "bash")
-
 	for _, tc := range []struct {
 		name     string
 		cmd      *Command
 		argv     []string
+		env      map[string]string
 		expected string
 	}{
 		{
 			name:     "empty",
 			cmd:      &Command{},
 			argv:     []string{"prog", "cmd"},
+			env:      map[string]string{"SHELL": "bash"},
 			expected: "",
 		},
 		{
@@ -1113,6 +1113,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 				},
 			},
 			argv:     []string{"cmd", "--e", "--generate-shell-completion"},
+			env:      map[string]string{"SHELL": "bash"},
 			expected: "--excitement\n",
 		},
 		{
@@ -1135,6 +1136,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 				},
 			},
 			argv:     []string{"cmd", "--generate-shell-completion"},
+			env:      map[string]string{"SHELL": "bash"},
 			expected: "futz\n",
 		},
 		{
@@ -1157,8 +1159,49 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 				},
 			},
 			argv:     []string{"cmd", "--url", "http://localhost:8000", "h", "--generate-shell-completion"},
+			env:      map[string]string{"SHELL": "bash"},
 			expected: "help\n",
 		},
+		{
+			name: "zsh-autocomplete-with-flag-descriptions",
+			cmd: &Command{
+				Name: "putz",
+				Flags: []Flag{
+					&BoolFlag{Name: "excitement", Usage: "an exciting flag"},
+					&StringFlag{Name: "hat-shape"},
+				},
+				parent: &Command{
+					Name: "cmd",
+					Flags: []Flag{
+						&BoolFlag{Name: "happiness"},
+						&IntFlag{Name: "everybody-jump-on"},
+					},
+				},
+			},
+			argv:     []string{"cmd", "putz", "-e", "--generate-shell-completion"},
+			env:      map[string]string{"SHELL": "zsh"},
+			expected: "--excitement:an exciting flag\n",
+		},
+		{
+			name: "zsh-autocomplete-with-empty-flag-descriptions",
+			cmd: &Command{
+				Name: "putz",
+				Flags: []Flag{
+					&BoolFlag{Name: "excitement"},
+					&StringFlag{Name: "hat-shape"},
+				},
+				parent: &Command{
+					Name: "cmd",
+					Flags: []Flag{
+						&BoolFlag{Name: "happiness"},
+						&IntFlag{Name: "everybody-jump-on"},
+					},
+				},
+			},
+			argv:     []string{"cmd", "putz", "-e", "--generate-shell-completion"},
+			env:      map[string]string{"SHELL": "zsh"},
+			expected: "--excitement\n",
+		},
 	} {
 		t.Run(tc.name, func(ct *testing.T) {
 			writer := &bytes.Buffer{}
@@ -1166,6 +1209,9 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
 			rootCmd.Writer = writer
 
 			os.Args = tc.argv
+			for k, v := range tc.env {
+				t.Setenv(k, v)
+			}
 			f := DefaultCompleteWithFlags(tc.cmd)
 			f(context.Background(), tc.cmd)