From 74d9ac7346f98955e32d7a7b1220193a14fb2b7c Mon Sep 17 00:00:00 2001 From: "kayos@tcp.direct" Date: Fri, 23 Dec 2022 06:52:49 -0800 Subject: [PATCH] Overhaul and fix CLI completion --- internal/cli/commands.go | 3 +- internal/cli/completer.go | 153 ++++++++++++++++++++------------------ internal/cli/process.go | 64 ++++++++-------- 3 files changed, 115 insertions(+), 105 deletions(-) diff --git a/internal/cli/commands.go b/internal/cli/commands.go index a47c19a..90c90ae 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -31,9 +31,10 @@ type ziggsCommand struct { description string aliases []string isAlias bool + requires int // number of arguments required } -func newZiggsCommand(react reactor, desc string, aliases ...string) *ziggsCommand { +func newZiggsCommand(react reactor, desc string, requires int, aliases ...string) *ziggsCommand { ret := &ziggsCommand{ reactor: react, aliases: aliases, diff --git a/internal/cli/completer.go b/internal/cli/completer.go index 484040b..0cd5f90 100644 --- a/internal/cli/completer.go +++ b/internal/cli/completer.go @@ -15,22 +15,18 @@ const ( type completion struct { cli.Suggest inner *ziggsCommand - requires map[int][]string + requires map[int]map[string]bool isAlias bool root bool } func (c completion) qualifies(line string) bool { args := strings.Fields(line) - if len(line) == 0 { - return false - } - if c.root && len(args) < 1 { + + if len(args) <= 1 && c.root { return true } - /*if c.root && len(args) > 0 { - return false - }*/ + if len(args) < len(c.requires) { if extraDebug { log.Trace().Int("len(args)", len(args)).Int("len(c.requires)", len(c.requires)). @@ -45,54 +41,60 @@ func (c completion) qualifies(line string) bool { } return false } - has := func(b []string, a string) bool { - for _, r := range b { - if a == r { - return true - } - } - return false - } + var count = 0 for i, a := range args { - if has(c.requires[i], a) { + i++ + if _, ok := c.requires[i][a]; ok { if extraDebug { log.Trace().Msgf("%v%s: found %s%v", grn, c.Text, a, rst) } count++ } } + + if extraDebug && !(count >= len(c.requires)) { + log.Trace().Msgf("%v%s: count(%d) < len(c.requires)(%d)", red, c.Text, count, len(c.requires)) + } + return count >= len(c.requires) } -var suggestions map[int][]*completion +var suggestions map[int]map[string]*completion func init() { - Commands["ls"] = newZiggsCommand(cmdList, "list all lights, groups, scenes, rules, and schedules") - Commands["schedules"] = newZiggsCommand(cmdSchedules, "list schedules", "lssched", "crontab") - Commands["rules"] = newZiggsCommand(cmdRules, "list rules", "lsrule") - Commands["sensors"] = newZiggsCommand(cmdSensors, "list sensors", "lssens") - Commands["scenes"] = newZiggsCommand(cmdScenes, "list scenes", "lsscene") - Commands["lights"] = newZiggsCommand(cmdLights, "list lights", "lslight") - Commands["groups"] = newZiggsCommand(cmdGroups, "list groups", "lsgrp") - Commands["create"] = newZiggsCommand(cmdCreate, "create a new object in bridge", "new", "mk") - Commands["delete"] = newZiggsCommand(cmdDelete, "delete objects from bridges", "del", "remove") - Commands["scan"] = newZiggsCommand(cmdScan, "scan for bridges/lights/sensors", "search", "find") - Commands["rename"] = newZiggsCommand(cmdRename, "rename object in bridge", "mv") - Commands["adopt"] = newZiggsCommand(cmdAdopt, "adopt new lights to the bridge") - Commands["dump"] = newZiggsCommand(cmdDump, "dump target object JSON to a file") - Commands["load"] = newZiggsCommand(cmdLoad, "load JSON from a file into the bridge") - Commands["set"] = newZiggsCommand(cmdSet, "update object properties in bridge") - Commands["fwupdate"] = newZiggsCommand(cmdFirmwareUpdate, "inform bridge to check for updates", - "fwup", "upgrade") - Commands["info"] = newZiggsCommand(cmdInfo, "show information about a bridge", "uname") + Commands["ls"] = newZiggsCommand(cmdList, "list all lights, groups, scenes, rules, and schedules", 0) + Commands["schedules"] = newZiggsCommand(cmdSchedules, "list schedules", 0, + "lssched", "crontab") + Commands["rules"] = newZiggsCommand(cmdRules, "list rules", 0, "lsrule") + Commands["sensors"] = newZiggsCommand(cmdSensors, "list sensors", 0, "lssens") + Commands["scenes"] = newZiggsCommand(cmdScenes, "list scenes", 0, "lsscene") + Commands["lights"] = newZiggsCommand(cmdLights, "list lights", 0, "lslight") + Commands["groups"] = newZiggsCommand(cmdGroups, "list groups", 0, "lsgrp") + Commands["create"] = newZiggsCommand(cmdCreate, "create a new object in bridge", 3, + "new", "mk") + Commands["delete"] = newZiggsCommand(cmdDelete, "delete objects from bridges", 2, + "del", "remove", "rm") + Commands["scan"] = newZiggsCommand(cmdScan, "scan for bridges/lights/sensors", 0, + "search", "find") + Commands["rename"] = newZiggsCommand(cmdRename, "rename object in bridge", 3, "mv") + Commands["adopt"] = newZiggsCommand(cmdAdopt, "adopt new lights to the bridge", 0) + Commands["dump"] = newZiggsCommand(cmdDump, "dump target object JSON to a file", 1) + Commands["load"] = newZiggsCommand(cmdLoad, "load JSON from a file into the bridge", 1) + Commands["set"] = newZiggsCommand(cmdSet, "update object properties in bridge", 3) + Commands["upgrade"] = newZiggsCommand(cmdFirmwareUpdate, "inform bridge to check for updates", 0, + "fwup", "upgrade", "fwupdate") + Commands["info"] = newZiggsCommand(cmdInfo, "show information about a bridge", 0, "uname") initCompletion() } func initCompletion() { - suggestions = make(map[int][]*completion) - suggestions[0] = []*completion{ - {Suggest: cli.Suggest{Text: "lights"}, inner: Commands["lights"]}, + suggestions = make(map[int]map[string]*completion) + suggestions[0] = make(map[string]*completion) + suggestions[1] = make(map[string]*completion) + suggestions[2] = make(map[string]*completion) + + /* {Suggest: cli.Suggest{Text: "lights"}, inner: Commands["lights"]}, {Suggest: cli.Suggest{Text: "groups"}, inner: Commands["groups"]}, {Suggest: cli.Suggest{Text: "rules"}, inner: Commands["rules"]}, {Suggest: cli.Suggest{Text: "scenes"}, inner: Commands["scenes"]}, @@ -107,63 +109,72 @@ func initCompletion() { {Suggest: cli.Suggest{Text: "dump"}, inner: Commands["dump"]}, {Suggest: cli.Suggest{Text: "load"}, inner: Commands["load"]}, {Suggest: cli.Suggest{Text: "use", Description: "select bridge to perform actions on"}}, - {Suggest: cli.Suggest{Text: "clear", Description: "clear screen"}}, + {Suggest: cli.Suggest{Text: "exit", Description: "exit ziggs"}}, + */ + + suggestions[0]["clear"] = &completion{Suggest: cli.Suggest{Text: "clear", Description: "clear screen"}} + suggestions[0]["exit"] = &completion{Suggest: cli.Suggest{Text: "exit", Description: "exit ziggs"}} + suggestions[0]["help"] = &completion{Suggest: cli.Suggest{Text: "help", Description: "show help"}} + + for name, cmd := range Commands { + suggestions[0][name] = &completion{Suggest: cli.Suggest{Text: name}, inner: cmd, root: cmd.requires == 0} + if cmd.description != "" { + suggestions[0][name].Description = cmd.description + } + suggestions[0][name].requires = map[int]map[string]bool{} } - for _, sug := range suggestions[0] { - sug.requires = map[int][]string{} - sug.root = true - if sug.inner != nil { - sug.Suggest.Description = sug.inner.description - } - if sug.inner != nil && len(sug.inner.aliases) > 0 { - for _, a := range sug.inner.aliases { - suggestions[0] = append(suggestions[0], &completion{ - Suggest: cli.Suggest{Text: a, Description: sug.Description}, - inner: sug.inner, - root: true, - isAlias: true, - }) - } - } - } - - suggestions[1] = []*completion{ - {Suggest: cli.Suggest{Text: "group", Description: "target group"}}, - {Suggest: cli.Suggest{Text: "light", Description: "target light"}}, - {Suggest: cli.Suggest{Text: "scene", Description: "target scene"}}, - {Suggest: cli.Suggest{Text: "schedule", Description: "target schedule"}}, - {Suggest: cli.Suggest{Text: "sensor", Description: "target sensor"}}, - {Suggest: cli.Suggest{Text: "config", Description: "target bridge config"}}, + suggestions[1] = map[string]*completion{ + "group": {Suggest: cli.Suggest{Text: "group", Description: "target group"}}, + "light": {Suggest: cli.Suggest{Text: "light", Description: "target light"}}, + "scene": {Suggest: cli.Suggest{Text: "scene", Description: "target scene"}}, + "schedule": {Suggest: cli.Suggest{Text: "schedule", Description: "target schedule"}}, + "sensor": {Suggest: cli.Suggest{Text: "sensor", Description: "target sensor"}}, + "config": {Suggest: cli.Suggest{Text: "config", Description: "target bridge config"}}, } for _, sug := range suggestions[1] { - sug.requires = map[int][]string{0: {"delete", "del", "set", "s", "rename", "mv", "dump", "load"}} + sug.requires = map[int]map[string]bool{1: { + "delete": true, "del": true, "set": true, "s": true, "rename": true, "mv": true, "dump": true, "load": true}, + } sug.root = false } delCompletion := []*completion{ {Suggest: cli.Suggest{Text: "scene", Description: "target scene"}}, {Suggest: cli.Suggest{Text: "schedule", Description: "target schedule"}}, {Suggest: cli.Suggest{Text: "sensor", Description: "target sensor"}}, + {Suggest: cli.Suggest{Text: "group", Description: "target group"}}, } for _, sug := range delCompletion { - sug.requires = map[int][]string{0: {"delete", "del"}} + sug.requires = map[int]map[string]bool{1: {"delete": true, "del": true}} sug.root = false } - suggestions[1] = append(suggestions[1], delCompletion...) } func completer(in cli.Document) []cli.Suggest { c := in.CurrentLine() + infields := strings.Fields(c) var head = len(infields) - 1 - if len(infields) == 0 { + if head < 0 { head = 0 } + if head == 1 { + head = 1 + } + if head > 0 && in.LastKeyStroke() == ' ' { + head++ + } + + if extraDebug { + log.Trace().Int("head", head).Msgf("completing %v", infields) + } var sugs []cli.Suggest for _, sug := range suggestions[head] { - if sug.qualifies(c) && strings.HasPrefix(sug.Text, in.GetWordBeforeCursor()) { - sugs = append(sugs, sug.Suggest) + if sug.qualifies(c) { + if in.GetWordBeforeCursor() != "" && strings.HasPrefix(sug.Text, in.GetWordBeforeCursor()) { + sugs = append(sugs, sug.Suggest) + } } } return sugs diff --git a/internal/cli/process.go b/internal/cli/process.go index 8dc3443..0c273d1 100644 --- a/internal/cli/process.go +++ b/internal/cli/process.go @@ -13,18 +13,18 @@ func processGroups(grps map[string]*huego.Group) { if g.Type != "" { suffix = " (" + g.Type + ")" } - suggestions[2] = append(suggestions[2], - &completion{ - Suggest: cli.Suggest{ - Text: grp, - Description: "Group" + suffix, - }, - requires: map[int][]string{ - 0: {"set", "s", "delete", "d"}, - 1: {"group", "g"}, - }, - root: false, - }) + + suggestions[2][grp] = &completion{ + Suggest: cli.Suggest{ + Text: grp, + Description: "Group" + suffix, + }, + requires: map[int]map[string]bool{ + 1: {"set": true, "s": true, "delete": true, "d": true}, + 2: {"group": true, "g": true}, + }, + root: false, + } } } @@ -34,31 +34,29 @@ func processLights() { if l.Type != "" { suffix = " (" + l.Type + ")" } - suggestions[2] = append(suggestions[2], - &completion{ - Suggest: cli.Suggest{ - Text: lt, - Description: "Light" + suffix, - }, - requires: map[int][]string{ - 0: {"set", "s", "delete", "d", "rename", "r"}, - 1: {"light", "l"}, - }, - root: false, - }) + suggestions[2][lt] = &completion{ + Suggest: cli.Suggest{ + Text: lt, + Description: "Light" + suffix, + }, + requires: map[int]map[string]bool{ + 1: {"set": true, "s": true, "delete": true, "d": true}, + 2: {"light": true, "l": true}, + }, + root: false, + } } } func processBridges() { for brd, b := range ziggy.Lucifer.Bridges { - suggestions[1] = append(suggestions[1], - &completion{ - Suggest: cli.Suggest{ - Text: brd, - Description: "Bridge: " + b.Host, - }, - requires: map[int][]string{0: {"use", "u"}}, - root: false, - }) + suggestions[1]["bridge"] = &completion{ + Suggest: cli.Suggest{ + Text: brd, + Description: "Bridge: " + b.Host, + }, + requires: map[int]map[string]bool{0: {"use": true, "u": true}}, + root: false, + } } }