completions.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. package cobra
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "sync"
  7. "github.com/spf13/pflag"
  8. )
  9. const (
  10. // ShellCompRequestCmd is the name of the hidden command that is used to request
  11. // completion results from the program. It is used by the shell completion scripts.
  12. ShellCompRequestCmd = "__complete"
  13. // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
  14. // completion results without their description. It is used by the shell completion scripts.
  15. ShellCompNoDescRequestCmd = "__completeNoDesc"
  16. )
  17. // Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
  18. var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){}
  19. // lock for reading and writing from flagCompletionFunctions
  20. var flagCompletionMutex = &sync.RWMutex{}
  21. // ShellCompDirective is a bit map representing the different behaviors the shell
  22. // can be instructed to have once completions have been provided.
  23. type ShellCompDirective int
  24. type flagCompError struct {
  25. subCommand string
  26. flagName string
  27. }
  28. func (e *flagCompError) Error() string {
  29. return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"
  30. }
  31. const (
  32. // ShellCompDirectiveError indicates an error occurred and completions should be ignored.
  33. ShellCompDirectiveError ShellCompDirective = 1 << iota
  34. // ShellCompDirectiveNoSpace indicates that the shell should not add a space
  35. // after the completion even if there is a single completion provided.
  36. ShellCompDirectiveNoSpace
  37. // ShellCompDirectiveNoFileComp indicates that the shell should not provide
  38. // file completion even when no completion is provided.
  39. ShellCompDirectiveNoFileComp
  40. // ShellCompDirectiveFilterFileExt indicates that the provided completions
  41. // should be used as file extension filters.
  42. // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
  43. // is a shortcut to using this directive explicitly. The BashCompFilenameExt
  44. // annotation can also be used to obtain the same behavior for flags.
  45. ShellCompDirectiveFilterFileExt
  46. // ShellCompDirectiveFilterDirs indicates that only directory names should
  47. // be provided in file completion. To request directory names within another
  48. // directory, the returned completions should specify the directory within
  49. // which to search. The BashCompSubdirsInDir annotation can be used to
  50. // obtain the same behavior but only for flags.
  51. ShellCompDirectiveFilterDirs
  52. // ===========================================================================
  53. // All directives using iota should be above this one.
  54. // For internal use.
  55. shellCompDirectiveMaxValue
  56. // ShellCompDirectiveDefault indicates to let the shell perform its default
  57. // behavior after completions have been provided.
  58. // This one must be last to avoid messing up the iota count.
  59. ShellCompDirectiveDefault ShellCompDirective = 0
  60. )
  61. const (
  62. // Constants for the completion command
  63. compCmdName = "completion"
  64. compCmdNoDescFlagName = "no-descriptions"
  65. compCmdNoDescFlagDesc = "disable completion descriptions"
  66. compCmdNoDescFlagDefault = false
  67. )
  68. // CompletionOptions are the options to control shell completion
  69. type CompletionOptions struct {
  70. // DisableDefaultCmd prevents Cobra from creating a default 'completion' command
  71. DisableDefaultCmd bool
  72. // DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
  73. // for shells that support completion descriptions
  74. DisableNoDescFlag bool
  75. // DisableDescriptions turns off all completion descriptions for shells
  76. // that support them
  77. DisableDescriptions bool
  78. }
  79. // NoFileCompletions can be used to disable file completion for commands that should
  80. // not trigger file completions.
  81. func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
  82. return nil, ShellCompDirectiveNoFileComp
  83. }
  84. // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
  85. func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error {
  86. flag := c.Flag(flagName)
  87. if flag == nil {
  88. return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
  89. }
  90. flagCompletionMutex.Lock()
  91. defer flagCompletionMutex.Unlock()
  92. if _, exists := flagCompletionFunctions[flag]; exists {
  93. return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
  94. }
  95. flagCompletionFunctions[flag] = f
  96. return nil
  97. }
  98. // Returns a string listing the different directive enabled in the specified parameter
  99. func (d ShellCompDirective) string() string {
  100. var directives []string
  101. if d&ShellCompDirectiveError != 0 {
  102. directives = append(directives, "ShellCompDirectiveError")
  103. }
  104. if d&ShellCompDirectiveNoSpace != 0 {
  105. directives = append(directives, "ShellCompDirectiveNoSpace")
  106. }
  107. if d&ShellCompDirectiveNoFileComp != 0 {
  108. directives = append(directives, "ShellCompDirectiveNoFileComp")
  109. }
  110. if d&ShellCompDirectiveFilterFileExt != 0 {
  111. directives = append(directives, "ShellCompDirectiveFilterFileExt")
  112. }
  113. if d&ShellCompDirectiveFilterDirs != 0 {
  114. directives = append(directives, "ShellCompDirectiveFilterDirs")
  115. }
  116. if len(directives) == 0 {
  117. directives = append(directives, "ShellCompDirectiveDefault")
  118. }
  119. if d >= shellCompDirectiveMaxValue {
  120. return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
  121. }
  122. return strings.Join(directives, ", ")
  123. }
  124. // Adds a special hidden command that can be used to request custom completions.
  125. func (c *Command) initCompleteCmd(args []string) {
  126. completeCmd := &Command{
  127. Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
  128. Aliases: []string{ShellCompNoDescRequestCmd},
  129. DisableFlagsInUseLine: true,
  130. Hidden: true,
  131. DisableFlagParsing: true,
  132. Args: MinimumNArgs(1),
  133. Short: "Request shell completion choices for the specified command-line",
  134. Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",
  135. "to request completion choices for the specified command-line.", ShellCompRequestCmd),
  136. Run: func(cmd *Command, args []string) {
  137. finalCmd, completions, directive, err := cmd.getCompletions(args)
  138. if err != nil {
  139. CompErrorln(err.Error())
  140. // Keep going for multiple reasons:
  141. // 1- There could be some valid completions even though there was an error
  142. // 2- Even without completions, we need to print the directive
  143. }
  144. noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd)
  145. for _, comp := range completions {
  146. if noDescriptions {
  147. // Remove any description that may be included following a tab character.
  148. comp = strings.Split(comp, "\t")[0]
  149. }
  150. // Make sure we only write the first line to the output.
  151. // This is needed if a description contains a linebreak.
  152. // Otherwise the shell scripts will interpret the other lines as new flags
  153. // and could therefore provide a wrong completion.
  154. comp = strings.Split(comp, "\n")[0]
  155. // Finally trim the completion. This is especially important to get rid
  156. // of a trailing tab when there are no description following it.
  157. // For example, a sub-command without a description should not be completed
  158. // with a tab at the end (or else zsh will show a -- following it
  159. // although there is no description).
  160. comp = strings.TrimSpace(comp)
  161. // Print each possible completion to stdout for the completion script to consume.
  162. fmt.Fprintln(finalCmd.OutOrStdout(), comp)
  163. }
  164. // As the last printout, print the completion directive for the completion script to parse.
  165. // The directive integer must be that last character following a single colon (:).
  166. // The completion script expects :<directive>
  167. fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive)
  168. // Print some helpful info to stderr for the user to understand.
  169. // Output from stderr must be ignored by the completion script.
  170. fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())
  171. },
  172. }
  173. c.AddCommand(completeCmd)
  174. subCmd, _, err := c.Find(args)
  175. if err != nil || subCmd.Name() != ShellCompRequestCmd {
  176. // Only create this special command if it is actually being called.
  177. // This reduces possible side-effects of creating such a command;
  178. // for example, having this command would cause problems to a
  179. // cobra program that only consists of the root command, since this
  180. // command would cause the root command to suddenly have a subcommand.
  181. c.RemoveCommand(completeCmd)
  182. }
  183. }
  184. func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
  185. // The last argument, which is not completely typed by the user,
  186. // should not be part of the list of arguments
  187. toComplete := args[len(args)-1]
  188. trimmedArgs := args[:len(args)-1]
  189. var finalCmd *Command
  190. var finalArgs []string
  191. var err error
  192. // Find the real command for which completion must be performed
  193. // check if we need to traverse here to parse local flags on parent commands
  194. if c.Root().TraverseChildren {
  195. finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
  196. } else {
  197. finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
  198. }
  199. if err != nil {
  200. // Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
  201. return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs)
  202. }
  203. finalCmd.ctx = c.ctx
  204. // Check if we are doing flag value completion before parsing the flags.
  205. // This is important because if we are completing a flag value, we need to also
  206. // remove the flag name argument from the list of finalArgs or else the parsing
  207. // could fail due to an invalid value (incomplete) for the flag.
  208. flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
  209. // Check if interspersed is false or -- was set on a previous arg.
  210. // This works by counting the arguments. Normally -- is not counted as arg but
  211. // if -- was already set or interspersed is false and there is already one arg then
  212. // the extra added -- is counted as arg.
  213. flagCompletion := true
  214. _ = finalCmd.ParseFlags(append(finalArgs, "--"))
  215. newArgCount := finalCmd.Flags().NArg()
  216. // Parse the flags early so we can check if required flags are set
  217. if err = finalCmd.ParseFlags(finalArgs); err != nil {
  218. return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
  219. }
  220. realArgCount := finalCmd.Flags().NArg()
  221. if newArgCount > realArgCount {
  222. // don't do flag completion (see above)
  223. flagCompletion = false
  224. }
  225. // Error while attempting to parse flags
  226. if flagErr != nil {
  227. // If error type is flagCompError and we don't want flagCompletion we should ignore the error
  228. if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
  229. return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr
  230. }
  231. }
  232. if flag != nil && flagCompletion {
  233. // Check if we are completing a flag value subject to annotations
  234. if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
  235. if len(validExts) != 0 {
  236. // File completion filtered by extensions
  237. return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
  238. }
  239. // The annotation requests simple file completion. There is no reason to do
  240. // that since it is the default behavior anyway. Let's ignore this annotation
  241. // in case the program also registered a completion function for this flag.
  242. // Even though it is a mistake on the program's side, let's be nice when we can.
  243. }
  244. if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
  245. if len(subDir) == 1 {
  246. // Directory completion from within a directory
  247. return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
  248. }
  249. // Directory completion
  250. return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
  251. }
  252. }
  253. // When doing completion of a flag name, as soon as an argument starts with
  254. // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires
  255. // the flag name to be complete
  256. if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
  257. var completions []string
  258. // First check for required flags
  259. completions = completeRequireFlags(finalCmd, toComplete)
  260. // If we have not found any required flags, only then can we show regular flags
  261. if len(completions) == 0 {
  262. doCompleteFlags := func(flag *pflag.Flag) {
  263. if !flag.Changed ||
  264. strings.Contains(flag.Value.Type(), "Slice") ||
  265. strings.Contains(flag.Value.Type(), "Array") {
  266. // If the flag is not already present, or if it can be specified multiple times (Array or Slice)
  267. // we suggest it as a completion
  268. completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
  269. }
  270. }
  271. // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
  272. // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
  273. // non-inherited flags.
  274. finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
  275. doCompleteFlags(flag)
  276. })
  277. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  278. doCompleteFlags(flag)
  279. })
  280. }
  281. directive := ShellCompDirectiveNoFileComp
  282. if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
  283. // If there is a single completion, the shell usually adds a space
  284. // after the completion. We don't want that if the flag ends with an =
  285. directive = ShellCompDirectiveNoSpace
  286. }
  287. return finalCmd, completions, directive, nil
  288. }
  289. // We only remove the flags from the arguments if DisableFlagParsing is not set.
  290. // This is important for commands which have requested to do their own flag completion.
  291. if !finalCmd.DisableFlagParsing {
  292. finalArgs = finalCmd.Flags().Args()
  293. }
  294. var completions []string
  295. directive := ShellCompDirectiveDefault
  296. if flag == nil {
  297. foundLocalNonPersistentFlag := false
  298. // If TraverseChildren is true on the root command we don't check for
  299. // local flags because we can use a local flag on a parent command
  300. if !finalCmd.Root().TraverseChildren {
  301. // Check if there are any local, non-persistent flags on the command-line
  302. localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
  303. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  304. if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
  305. foundLocalNonPersistentFlag = true
  306. }
  307. })
  308. }
  309. // Complete subcommand names, including the help command
  310. if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
  311. // We only complete sub-commands if:
  312. // - there are no arguments on the command-line and
  313. // - there are no local, non-persistent flags on the command-line or TraverseChildren is true
  314. for _, subCmd := range finalCmd.Commands() {
  315. if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
  316. if strings.HasPrefix(subCmd.Name(), toComplete) {
  317. completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
  318. }
  319. directive = ShellCompDirectiveNoFileComp
  320. }
  321. }
  322. }
  323. // Complete required flags even without the '-' prefix
  324. completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
  325. // Always complete ValidArgs, even if we are completing a subcommand name.
  326. // This is for commands that have both subcommands and ValidArgs.
  327. if len(finalCmd.ValidArgs) > 0 {
  328. if len(finalArgs) == 0 {
  329. // ValidArgs are only for the first argument
  330. for _, validArg := range finalCmd.ValidArgs {
  331. if strings.HasPrefix(validArg, toComplete) {
  332. completions = append(completions, validArg)
  333. }
  334. }
  335. directive = ShellCompDirectiveNoFileComp
  336. // If no completions were found within commands or ValidArgs,
  337. // see if there are any ArgAliases that should be completed.
  338. if len(completions) == 0 {
  339. for _, argAlias := range finalCmd.ArgAliases {
  340. if strings.HasPrefix(argAlias, toComplete) {
  341. completions = append(completions, argAlias)
  342. }
  343. }
  344. }
  345. }
  346. // If there are ValidArgs specified (even if they don't match), we stop completion.
  347. // Only one of ValidArgs or ValidArgsFunction can be used for a single command.
  348. return finalCmd, completions, directive, nil
  349. }
  350. // Let the logic continue so as to add any ValidArgsFunction completions,
  351. // even if we already found sub-commands.
  352. // This is for commands that have subcommands but also specify a ValidArgsFunction.
  353. }
  354. // Find the completion function for the flag or command
  355. var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
  356. if flag != nil && flagCompletion {
  357. flagCompletionMutex.RLock()
  358. completionFn = flagCompletionFunctions[flag]
  359. flagCompletionMutex.RUnlock()
  360. } else {
  361. completionFn = finalCmd.ValidArgsFunction
  362. }
  363. if completionFn != nil {
  364. // Go custom completion defined for this flag or command.
  365. // Call the registered completion function to get the completions.
  366. var comps []string
  367. comps, directive = completionFn(finalCmd, finalArgs, toComplete)
  368. completions = append(completions, comps...)
  369. }
  370. return finalCmd, completions, directive, nil
  371. }
  372. func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
  373. if nonCompletableFlag(flag) {
  374. return []string{}
  375. }
  376. var completions []string
  377. flagName := "--" + flag.Name
  378. if strings.HasPrefix(flagName, toComplete) {
  379. // Flag without the =
  380. completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
  381. // Why suggest both long forms: --flag and --flag= ?
  382. // This forces the user to *always* have to type either an = or a space after the flag name.
  383. // Let's be nice and avoid making users have to do that.
  384. // Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
  385. // The = form will still work, we just won't suggest it.
  386. // This also makes the list of suggested flags shorter as we avoid all the = forms.
  387. //
  388. // if len(flag.NoOptDefVal) == 0 {
  389. // // Flag requires a value, so it can be suffixed with =
  390. // flagName += "="
  391. // completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
  392. // }
  393. }
  394. flagName = "-" + flag.Shorthand
  395. if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
  396. completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage))
  397. }
  398. return completions
  399. }
  400. func completeRequireFlags(finalCmd *Command, toComplete string) []string {
  401. var completions []string
  402. doCompleteRequiredFlags := func(flag *pflag.Flag) {
  403. if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
  404. if !flag.Changed {
  405. // If the flag is not already present, we suggest it as a completion
  406. completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
  407. }
  408. }
  409. }
  410. // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
  411. // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
  412. // non-inherited flags.
  413. finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
  414. doCompleteRequiredFlags(flag)
  415. })
  416. finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
  417. doCompleteRequiredFlags(flag)
  418. })
  419. return completions
  420. }
  421. func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
  422. if finalCmd.DisableFlagParsing {
  423. // We only do flag completion if we are allowed to parse flags
  424. // This is important for commands which have requested to do their own flag completion.
  425. return nil, args, lastArg, nil
  426. }
  427. var flagName string
  428. trimmedArgs := args
  429. flagWithEqual := false
  430. orgLastArg := lastArg
  431. // When doing completion of a flag name, as soon as an argument starts with
  432. // a '-' we know it is a flag. We cannot use isFlagArg() here as that function
  433. // requires the flag name to be complete
  434. if len(lastArg) > 0 && lastArg[0] == '-' {
  435. if index := strings.Index(lastArg, "="); index >= 0 {
  436. // Flag with an =
  437. if strings.HasPrefix(lastArg[:index], "--") {
  438. // Flag has full name
  439. flagName = lastArg[2:index]
  440. } else {
  441. // Flag is shorthand
  442. // We have to get the last shorthand flag name
  443. // e.g. `-asd` => d to provide the correct completion
  444. // https://github.com/spf13/cobra/issues/1257
  445. flagName = lastArg[index-1 : index]
  446. }
  447. lastArg = lastArg[index+1:]
  448. flagWithEqual = true
  449. } else {
  450. // Normal flag completion
  451. return nil, args, lastArg, nil
  452. }
  453. }
  454. if len(flagName) == 0 {
  455. if len(args) > 0 {
  456. prevArg := args[len(args)-1]
  457. if isFlagArg(prevArg) {
  458. // Only consider the case where the flag does not contain an =.
  459. // If the flag contains an = it means it has already been fully processed,
  460. // so we don't need to deal with it here.
  461. if index := strings.Index(prevArg, "="); index < 0 {
  462. if strings.HasPrefix(prevArg, "--") {
  463. // Flag has full name
  464. flagName = prevArg[2:]
  465. } else {
  466. // Flag is shorthand
  467. // We have to get the last shorthand flag name
  468. // e.g. `-asd` => d to provide the correct completion
  469. // https://github.com/spf13/cobra/issues/1257
  470. flagName = prevArg[len(prevArg)-1:]
  471. }
  472. // Remove the uncompleted flag or else there could be an error created
  473. // for an invalid value for that flag
  474. trimmedArgs = args[:len(args)-1]
  475. }
  476. }
  477. }
  478. }
  479. if len(flagName) == 0 {
  480. // Not doing flag completion
  481. return nil, trimmedArgs, lastArg, nil
  482. }
  483. flag := findFlag(finalCmd, flagName)
  484. if flag == nil {
  485. // Flag not supported by this command, the interspersed option might be set so return the original args
  486. return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}
  487. }
  488. if !flagWithEqual {
  489. if len(flag.NoOptDefVal) != 0 {
  490. // We had assumed dealing with a two-word flag but the flag is a boolean flag.
  491. // In that case, there is no value following it, so we are not really doing flag completion.
  492. // Reset everything to do noun completion.
  493. trimmedArgs = args
  494. flag = nil
  495. }
  496. }
  497. return flag, trimmedArgs, lastArg, nil
  498. }
  499. // initDefaultCompletionCmd adds a default 'completion' command to c.
  500. // This function will do nothing if any of the following is true:
  501. // 1- the feature has been explicitly disabled by the program,
  502. // 2- c has no subcommands (to avoid creating one),
  503. // 3- c already has a 'completion' command provided by the program.
  504. func (c *Command) initDefaultCompletionCmd() {
  505. if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() {
  506. return
  507. }
  508. for _, cmd := range c.commands {
  509. if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {
  510. // A completion command is already available
  511. return
  512. }
  513. }
  514. haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
  515. completionCmd := &Command{
  516. Use: compCmdName,
  517. Short: "generate the autocompletion script for the specified shell",
  518. Long: fmt.Sprintf(`
  519. Generate the autocompletion script for %[1]s for the specified shell.
  520. See each sub-command's help for details on how to use the generated script.
  521. `, c.Root().Name()),
  522. Args: NoArgs,
  523. ValidArgsFunction: NoFileCompletions,
  524. }
  525. c.AddCommand(completionCmd)
  526. out := c.OutOrStdout()
  527. noDesc := c.CompletionOptions.DisableDescriptions
  528. shortDesc := "generate the autocompletion script for %s"
  529. bash := &Command{
  530. Use: "bash",
  531. Short: fmt.Sprintf(shortDesc, "bash"),
  532. Long: fmt.Sprintf(`
  533. Generate the autocompletion script for the bash shell.
  534. This script depends on the 'bash-completion' package.
  535. If it is not installed already, you can install it via your OS's package manager.
  536. To load completions in your current shell session:
  537. $ source <(%[1]s completion bash)
  538. To load completions for every new session, execute once:
  539. Linux:
  540. $ %[1]s completion bash > /etc/bash_completion.d/%[1]s
  541. MacOS:
  542. $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s
  543. You will need to start a new shell for this setup to take effect.
  544. `, c.Root().Name()),
  545. Args: NoArgs,
  546. DisableFlagsInUseLine: true,
  547. ValidArgsFunction: NoFileCompletions,
  548. RunE: func(cmd *Command, args []string) error {
  549. return cmd.Root().GenBashCompletionV2(out, !noDesc)
  550. },
  551. }
  552. if haveNoDescFlag {
  553. bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  554. }
  555. zsh := &Command{
  556. Use: "zsh",
  557. Short: fmt.Sprintf(shortDesc, "zsh"),
  558. Long: fmt.Sprintf(`
  559. Generate the autocompletion script for the zsh shell.
  560. If shell completion is not already enabled in your environment you will need
  561. to enable it. You can execute the following once:
  562. $ echo "autoload -U compinit; compinit" >> ~/.zshrc
  563. To load completions for every new session, execute once:
  564. # Linux:
  565. $ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
  566. # macOS:
  567. $ %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s
  568. You will need to start a new shell for this setup to take effect.
  569. `, c.Root().Name()),
  570. Args: NoArgs,
  571. ValidArgsFunction: NoFileCompletions,
  572. RunE: func(cmd *Command, args []string) error {
  573. if noDesc {
  574. return cmd.Root().GenZshCompletionNoDesc(out)
  575. }
  576. return cmd.Root().GenZshCompletion(out)
  577. },
  578. }
  579. if haveNoDescFlag {
  580. zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  581. }
  582. fish := &Command{
  583. Use: "fish",
  584. Short: fmt.Sprintf(shortDesc, "fish"),
  585. Long: fmt.Sprintf(`
  586. Generate the autocompletion script for the fish shell.
  587. To load completions in your current shell session:
  588. $ %[1]s completion fish | source
  589. To load completions for every new session, execute once:
  590. $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
  591. You will need to start a new shell for this setup to take effect.
  592. `, c.Root().Name()),
  593. Args: NoArgs,
  594. ValidArgsFunction: NoFileCompletions,
  595. RunE: func(cmd *Command, args []string) error {
  596. return cmd.Root().GenFishCompletion(out, !noDesc)
  597. },
  598. }
  599. if haveNoDescFlag {
  600. fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  601. }
  602. powershell := &Command{
  603. Use: "powershell",
  604. Short: fmt.Sprintf(shortDesc, "powershell"),
  605. Long: fmt.Sprintf(`
  606. Generate the autocompletion script for powershell.
  607. To load completions in your current shell session:
  608. PS C:\> %[1]s completion powershell | Out-String | Invoke-Expression
  609. To load completions for every new session, add the output of the above command
  610. to your powershell profile.
  611. `, c.Root().Name()),
  612. Args: NoArgs,
  613. ValidArgsFunction: NoFileCompletions,
  614. RunE: func(cmd *Command, args []string) error {
  615. if noDesc {
  616. return cmd.Root().GenPowerShellCompletion(out)
  617. }
  618. return cmd.Root().GenPowerShellCompletionWithDesc(out)
  619. },
  620. }
  621. if haveNoDescFlag {
  622. powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
  623. }
  624. completionCmd.AddCommand(bash, zsh, fish, powershell)
  625. }
  626. func findFlag(cmd *Command, name string) *pflag.Flag {
  627. flagSet := cmd.Flags()
  628. if len(name) == 1 {
  629. // First convert the short flag into a long flag
  630. // as the cmd.Flag() search only accepts long flags
  631. if short := flagSet.ShorthandLookup(name); short != nil {
  632. name = short.Name
  633. } else {
  634. set := cmd.InheritedFlags()
  635. if short = set.ShorthandLookup(name); short != nil {
  636. name = short.Name
  637. } else {
  638. return nil
  639. }
  640. }
  641. }
  642. return cmd.Flag(name)
  643. }
  644. // CompDebug prints the specified string to the same file as where the
  645. // completion script prints its logs.
  646. // Note that completion printouts should never be on stdout as they would
  647. // be wrongly interpreted as actual completion choices by the completion script.
  648. func CompDebug(msg string, printToStdErr bool) {
  649. msg = fmt.Sprintf("[Debug] %s", msg)
  650. // Such logs are only printed when the user has set the environment
  651. // variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
  652. if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
  653. f, err := os.OpenFile(path,
  654. os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
  655. if err == nil {
  656. defer f.Close()
  657. WriteStringAndCheck(f, msg)
  658. }
  659. }
  660. if printToStdErr {
  661. // Must print to stderr for this not to be read by the completion script.
  662. fmt.Fprint(os.Stderr, msg)
  663. }
  664. }
  665. // CompDebugln prints the specified string with a newline at the end
  666. // to the same file as where the completion script prints its logs.
  667. // Such logs are only printed when the user has set the environment
  668. // variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
  669. func CompDebugln(msg string, printToStdErr bool) {
  670. CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)
  671. }
  672. // CompError prints the specified completion message to stderr.
  673. func CompError(msg string) {
  674. msg = fmt.Sprintf("[Error] %s", msg)
  675. CompDebug(msg, true)
  676. }
  677. // CompErrorln prints the specified completion message to stderr with a newline at the end.
  678. func CompErrorln(msg string) {
  679. CompError(fmt.Sprintf("%s\n", msg))
  680. }