| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- package main
- import (
- "os"
- "sort"
- "strconv"
- "strings"
- "github.com/spf13/cobra"
- intrgb "netdome.biz/paul/qmk-rgb/internal/rgb"
- "netdome.biz/paul/qmk-rgb/internal/via"
- )
- // Shell completion. The tool knows how to resolve a zone name and which profiles
- // exist, and a shell only offers what the binary tells it about, so the binary has
- // to say it. None of these open the keyboard: a shell asks while nothing is
- // plugged in, and an error printed into a prompt is worse than an empty list.
- // completeZoneNames offers the QMK subsystem names, which follow from the channel
- // number and work on any board, plus the names the definition files give their
- // channels, which are what a board calls them in VIA.
- func completeZoneNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- var names []string
- for _, ch := range via.LightingChannels {
- names = append(names, ch.Subsystem())
- }
- if dir, err := definitionsDir(); err == nil {
- if defs, loadErr := intrgb.LoadDefinitionsDir(dir); loadErr == nil {
- for _, def := range defs {
- for _, label := range def.Labels {
- names = append(names, label)
- }
- }
- }
- }
- return narrow(names, toComplete), cobra.ShellCompDirectiveNoFileComp
- }
- // completeDeviceNumbers offers the keyboard numbers keyboard info prints, which
- // come from enumeration and need no open keyboard.
- func completeDeviceNumbers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- devices, err := discoverAll()
- if err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- var numbers []string
- for _, d := range devices {
- numbers = append(numbers, strconv.Itoa(d.Index))
- }
- return narrow(numbers, toComplete), cobra.ShellCompDirectiveNoFileComp
- }
- // completeProfileNames offers the profiles in the directory, because load and
- // delete take a name that is a file there.
- func completeProfileNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- entries, err := os.ReadDir(profilesDir)
- if err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- var names []string
- for _, entry := range entries {
- if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
- continue
- }
- names = append(names, strings.TrimSuffix(entry.Name(), ".json"))
- }
- return narrow(names, toComplete), cobra.ShellCompDirectiveNoFileComp
- }
- // narrow keeps the candidates a prefix matches, in order and without repeats: a
- // completion list with duplicates reads as a bug, and one that ignores what has
- // been typed makes the shell do the work it came here for.
- func narrow(names []string, prefix string) []string {
- seen := make(map[string]bool, len(names))
- var out []string
- for _, name := range names {
- if prefix != "" && !strings.HasPrefix(name, prefix) {
- continue
- }
- if seen[name] {
- continue
- }
- seen[name] = true
- out = append(out, name)
- }
- sort.Strings(out)
- return out
- }
- // registerFlagCompletions teaches the shell what the persistent flags accept. The
- // definition file is a path, so the shell completes paths for it on its own.
- func registerFlagCompletions(root *cobra.Command) {
- _ = root.RegisterFlagCompletionFunc("zone", completeZoneNames)
- _ = root.RegisterFlagCompletionFunc("device", completeDeviceNumbers)
- _ = root.MarkFlagFilename("definition", "*.json")
- }
|