| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- package main
- import (
- "sort"
- "strconv"
- "strings"
- "github.com/spf13/cobra"
- "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 word `all`, then the QMK subsystem names, which
- // follow from the channel number and work on any board, then 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) {
- names := []string{zoneAll}
- for _, ch := range via.LightingChannels {
- names = append(names, ch.Subsystem())
- }
- // Every definition the tool knows, not only the ones in the data directory:
- // the Impact 80's file is built into the binary, so a board that names its
- // channels logo, Backlight and side offers those names in a shell that has
- // never been given a definitions directory. A name the user cannot see is a
- // name they will not type.
- for _, def := range candidateDefinitions() {
- for _, label := range def.Labels {
- names = append(names, label)
- }
- }
- return narrow(names, toComplete), cobra.ShellCompDirectiveNoFileComp
- }
- // completeZoneArgs offers zone names for a command's zone and nothing after it.
- // The effect name `effect` takes second would have to come from a definition
- // file chosen for the connected board, and a shell asks while nothing is
- // plugged in, so a list built without a keyboard is the whole of what can be
- // offered honestly.
- func completeZoneArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) > 0 {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- return completeZoneNames(cmd, args, toComplete)
- }
- // completeLoadArgs offers a profile name first and a zone second, which is the
- // order the command reads them in. The zone goes through completeZoneNames
- // directly rather than through completeZoneArgs, which stops after one argument
- // because for every other command the zone is that first argument.
- func completeLoadArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- if len(args) == 0 {
- return completeProfileNames(cmd, args, toComplete)
- }
- return completeZoneNames(cmd, args, toComplete)
- }
- // 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 that can be read, because load and
- // delete take a name that is a file in the profiles directory.
- func completeProfileNames(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- names, err := ListProfiles()
- if err != nil {
- return nil, cobra.ShellCompDirectiveNoFileComp
- }
- 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. The
- // zones are positional, so they are completed by the commands that take one, in
- // completeZoneArgs.
- func registerFlagCompletions(root *cobra.Command) {
- _ = root.RegisterFlagCompletionFunc("device", completeDeviceNumbers)
- _ = root.MarkFlagFilename("definition", "*.json")
- }
|