@@ -23,7 +23,7 @@ type APIClientProvider interface {
2323
2424// ImageNames offers completion for images present within the local store
2525func ImageNames (dockerCLI APIClientProvider , limit int ) cobra.CompletionFunc {
26- return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
26+ return Unique ( func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
2727 if limit > 0 && len (args ) >= limit {
2828 return nil , cobra .ShellCompDirectiveNoFileComp
2929 }
@@ -36,14 +36,14 @@ func ImageNames(dockerCLI APIClientProvider, limit int) cobra.CompletionFunc {
3636 names = append (names , img .RepoTags ... )
3737 }
3838 return names , cobra .ShellCompDirectiveNoFileComp
39- }
39+ })
4040}
4141
4242// ImageNamesWithBase offers completion for images present within the local store,
4343// including both full image names with tags and base image names (repository names only)
4444// when multiple tags exist for the same base name
4545func ImageNamesWithBase (dockerCLI APIClientProvider , limit int ) cobra.CompletionFunc {
46- return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
46+ return Unique ( func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
4747 if limit > 0 && len (args ) >= limit {
4848 return nil , cobra .ShellCompDirectiveNoFileComp
4949 }
@@ -69,14 +69,14 @@ func ImageNamesWithBase(dockerCLI APIClientProvider, limit int) cobra.Completion
6969 }
7070 }
7171 return names , cobra .ShellCompDirectiveNoSpace | cobra .ShellCompDirectiveNoFileComp
72- }
72+ })
7373}
7474
7575// ContainerNames offers completion for container names and IDs
7676// By default, only names are returned.
7777// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
7878func ContainerNames (dockerCLI APIClientProvider , all bool , filters ... func (container.Summary ) bool ) cobra.CompletionFunc {
79- return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
79+ return Unique ( func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
8080 res , err := dockerCLI .Client ().ContainerList (cmd .Context (), client.ContainerListOptions {
8181 All : all ,
8282 })
@@ -104,12 +104,12 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
104104 names = append (names , formatter .StripNamePrefix (ctr .Names )... )
105105 }
106106 return names , cobra .ShellCompDirectiveNoFileComp
107- }
107+ })
108108}
109109
110110// VolumeNames offers completion for volumes
111111func VolumeNames (dockerCLI APIClientProvider ) cobra.CompletionFunc {
112- return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
112+ return Unique ( func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
113113 res , err := dockerCLI .Client ().VolumeList (cmd .Context (), client.VolumeListOptions {})
114114 if err != nil {
115115 return nil , cobra .ShellCompDirectiveError
@@ -119,12 +119,12 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
119119 names = append (names , vol .Name )
120120 }
121121 return names , cobra .ShellCompDirectiveNoFileComp
122- }
122+ })
123123}
124124
125125// NetworkNames offers completion for networks
126126func NetworkNames (dockerCLI APIClientProvider ) cobra.CompletionFunc {
127- return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
127+ return Unique ( func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
128128 res , err := dockerCLI .Client ().NetworkList (cmd .Context (), client.NetworkListOptions {})
129129 if err != nil {
130130 return nil , cobra .ShellCompDirectiveError
@@ -134,7 +134,7 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
134134 names = append (names , nw .Name )
135135 }
136136 return names , cobra .ShellCompDirectiveNoFileComp
137- }
137+ })
138138}
139139
140140// EnvVarNames offers completion for environment-variable names. This
@@ -151,20 +151,20 @@ func NetworkNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
151151// docker run --rm --env MY_VAR alpine printenv MY_VAR
152152// hello
153153func EnvVarNames () cobra.CompletionFunc {
154- return func (_ * cobra.Command , _ []string , _ string ) (names []string , _ cobra.ShellCompDirective ) {
154+ return Unique ( func (_ * cobra.Command , _ []string , _ string ) (names []string , _ cobra.ShellCompDirective ) {
155155 envs := os .Environ ()
156156 names = make ([]string , 0 , len (envs ))
157157 for _ , env := range envs {
158158 name , _ , _ := strings .Cut (env , "=" )
159159 names = append (names , name )
160160 }
161161 return names , cobra .ShellCompDirectiveNoFileComp
162- }
162+ })
163163}
164164
165165// FromList offers completion for the given list of options.
166166func FromList (options ... string ) cobra.CompletionFunc {
167- return cobra .FixedCompletions (options , cobra .ShellCompDirectiveNoFileComp )
167+ return Unique ( cobra .FixedCompletions (options , cobra .ShellCompDirectiveNoFileComp ) )
168168}
169169
170170// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
@@ -218,3 +218,38 @@ func Platforms() cobra.CompletionFunc {
218218 return commonPlatforms , cobra .ShellCompDirectiveNoFileComp
219219 }
220220}
221+
222+ // Unique wraps a completion func and removes completion results that are
223+ // already consumed (i.e., appear in "args").
224+ //
225+ // For example:
226+ //
227+ // # initial completion: args is empty, so all results are shown
228+ // command <tab>
229+ // one two three
230+ //
231+ // # "one" is already used so omitted
232+ // command one <tab>
233+ // two three
234+ func Unique (fn cobra.CompletionFunc ) cobra.CompletionFunc {
235+ return func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
236+ all , dir := fn (cmd , args , toComplete )
237+ if len (all ) == 0 || len (args ) == 0 {
238+ return all , dir
239+ }
240+
241+ alreadyCompleted := make (map [string ]struct {}, len (args ))
242+ for _ , a := range args {
243+ alreadyCompleted [a ] = struct {}{}
244+ }
245+
246+ out := make ([]string , 0 , len (all ))
247+ for _ , c := range all {
248+ if _ , ok := alreadyCompleted [c ]; ! ok {
249+ out = append (out , c )
250+ }
251+ }
252+
253+ return out , dir
254+ }
255+ }
0 commit comments