completion.go 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package main
  2. import (
  3. "sort"
  4. "strconv"
  5. "strings"
  6. "github.com/spf13/cobra"
  7. intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
  8. "netdome.biz/paul/qmk-rgb/internal/via"
  9. )
  10. // Shell completion. The tool knows how to resolve a zone name and which profiles
  11. // exist, and a shell only offers what the binary tells it about, so the binary has
  12. // to say it. None of these open the keyboard: a shell asks while nothing is
  13. // plugged in, and an error printed into a prompt is worse than an empty list.
  14. // completeZoneNames offers the QMK subsystem names, which follow from the channel
  15. // number and work on any board, plus the names the definition files give their
  16. // channels, which are what a board calls them in VIA.
  17. func completeZoneNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  18. var names []string
  19. for _, ch := range via.LightingChannels {
  20. names = append(names, ch.Subsystem())
  21. }
  22. if defs, err := intrgb.LoadDefinitionsDir(definitionsPath()); err == nil {
  23. for _, def := range defs {
  24. for _, label := range def.Labels {
  25. names = append(names, label)
  26. }
  27. }
  28. }
  29. return narrow(names, toComplete), cobra.ShellCompDirectiveNoFileComp
  30. }
  31. // completeDeviceNumbers offers the keyboard numbers keyboard info prints, which
  32. // come from enumeration and need no open keyboard.
  33. func completeDeviceNumbers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  34. devices, err := discoverAll()
  35. if err != nil {
  36. return nil, cobra.ShellCompDirectiveNoFileComp
  37. }
  38. var numbers []string
  39. for _, d := range devices {
  40. numbers = append(numbers, strconv.Itoa(d.Index))
  41. }
  42. return narrow(numbers, toComplete), cobra.ShellCompDirectiveNoFileComp
  43. }
  44. // completeProfileNames offers the profiles that can be read, because load and
  45. // delete take a name that is a file in the profiles directory.
  46. func completeProfileNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
  47. names, err := ListProfiles()
  48. if err != nil {
  49. return nil, cobra.ShellCompDirectiveNoFileComp
  50. }
  51. return narrow(names, toComplete), cobra.ShellCompDirectiveNoFileComp
  52. }
  53. // narrow keeps the candidates a prefix matches, in order and without repeats: a
  54. // completion list with duplicates reads as a bug, and one that ignores what has
  55. // been typed makes the shell do the work it came here for.
  56. func narrow(names []string, prefix string) []string {
  57. seen := make(map[string]bool, len(names))
  58. var out []string
  59. for _, name := range names {
  60. if prefix != "" && !strings.HasPrefix(name, prefix) {
  61. continue
  62. }
  63. if seen[name] {
  64. continue
  65. }
  66. seen[name] = true
  67. out = append(out, name)
  68. }
  69. sort.Strings(out)
  70. return out
  71. }
  72. // registerFlagCompletions teaches the shell what the persistent flags accept. The
  73. // definition file is a path, so the shell completes paths for it on its own.
  74. func registerFlagCompletions(root *cobra.Command) {
  75. _ = root.RegisterFlagCompletionFunc("zone", completeZoneNames)
  76. _ = root.RegisterFlagCompletionFunc("device", completeDeviceNumbers)
  77. _ = root.MarkFlagFilename("definition", "*.json")
  78. }