aboutsummaryrefslogtreecommitdiffstats
path: root/commands/commands.go
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2023-05-10 23:56:23 +0200
committerRobin Jarry <robin@jarry.cc>2023-05-16 13:39:17 +0200
commit5c9d22bb84eb8a6ddd4477bae3c081964d6e7b51 (patch)
tree263083c1f55d60d55d2b005d6504c64393432186 /commands/commands.go
parentcd68adc4630ed834eeac33f09ef58891c18c2dee (diff)
downloadaerc-5c9d22bb84eb8a6ddd4477bae3c081964d6e7b51.tar.gz
commands: add OptionsProvider and OptionCompleter
Improve command completion by supporting option flags and option arguments completion. Option completion is activated when the command implements the OptionsProvider interface. Implementing the OptionCompleter allows the completion of individual option arguments. The completion interfaces harmonizes the completion behavior in aerc, makes the completion code clearer and simplifies the completion functionality. With this patch, the Complete method on commands will only have to deal with the actual completion, i.e. paths, folders, etc and not worry about options. To remove all options and its mandatory arguments from args, use commands.Operands(). Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'commands/commands.go')
-rw-r--r--commands/commands.go133
1 files changed, 104 insertions, 29 deletions
diff --git a/commands/commands.go b/commands/commands.go
index 0a7050e9..f50a1e8a 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -23,6 +23,16 @@ type Command interface {
Complete(*widgets.Aerc, []string) []string
}
+type OptionsProvider interface {
+ Command
+ Options() string
+}
+
+type OptionCompleter interface {
+ OptionsProvider
+ CompleteOption(*widgets.Aerc, rune, string) []string
+}
+
type Commands map[string]Command
func NewCommands() *Commands {
@@ -131,49 +141,98 @@ func (cmds *Commands) ExecuteCommand(
return NoSuchCommand(args[0])
}
-func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string {
- args, err := shlex.Split(cmd)
+// GetCompletions returns the completion options and the command prefix
+func (cmds *Commands) GetCompletions(
+ aerc *widgets.Aerc, cmd string,
+) (options []string, prefix string) {
+ log.Tracef("completing command: %s", cmd)
+
+ // start completion
+ args, err := splitCmd(cmd)
if err != nil {
- return nil
+ return
}
// nothing entered, list all commands
if len(args) == 0 {
- names := cmds.Names()
- sort.Strings(names)
- return names
+ options = cmds.Names()
+ sort.Strings(options)
+ return
+ }
+
+ // complete command name
+ spaceTerminated := cmd[len(cmd)-1] == ' '
+ if len(args) == 1 && !spaceTerminated {
+ for _, n := range cmds.Names() {
+ options = append(options, n+" ")
+ }
+ options = CompletionFromList(aerc, options, args)
+
+ return
+ }
+
+ // look for command in dictionary
+ c, ok := cmds.dict()[args[0]]
+ if !ok {
+ return
}
// complete options
- if len(args) > 1 || cmd[len(cmd)-1] == ' ' {
- if cmd, ok := cmds.dict()[args[0]]; ok {
- var completions []string
- if len(args) > 1 {
- completions = cmd.Complete(aerc, args[1:])
- } else {
- completions = cmd.Complete(aerc, []string{})
+ var spec string
+ if provider, ok := c.(OptionsProvider); ok {
+ spec = provider.Options()
+ }
+
+ parser, err := newParser(cmd, spec, spaceTerminated)
+ if err != nil {
+ log.Debugf("completion parser failed: %v", err)
+ return
+ }
+
+ switch parser.kind {
+ case SHORT_OPTION:
+ for _, r := range strings.ReplaceAll(spec, ":", "") {
+ if strings.ContainsRune(parser.flag, r) {
+ continue
}
- if completions != nil && len(completions) == 0 {
- return nil
+ option := string(r)
+ if strings.Contains(spec, option+":") {
+ option += " "
}
-
- options := make([]string, 0)
- for _, option := range completions {
- options = append(options, args[0]+" "+option)
+ options = append(options, option)
+ }
+ prefix = cmd
+ case OPTION_ARGUMENT:
+ cmpl, ok := c.(OptionCompleter)
+ if !ok {
+ return
+ }
+ stem := cmd
+ if parser.arg != "" {
+ stem = strings.TrimSuffix(cmd, parser.arg)
+ }
+ pad := ""
+ if !strings.HasSuffix(stem, " ") {
+ pad += " "
+ }
+ s := parser.flag
+ r := rune(s[len(s)-1])
+ for _, option := range cmpl.CompleteOption(aerc, r, parser.arg) {
+ options = append(options, pad+escape(option)+" ")
+ }
+ prefix = stem
+ case OPERAND:
+ stem := strings.Join(args[:parser.optind], " ")
+ for _, option := range c.Complete(aerc, args[1:]) {
+ if strings.Contains(option, " ") {
+ option = escape(option)
}
- return options
+ options = append(options, " "+option)
}
- return nil
+ prefix = stem
}
- // complete available commands
- names := cmds.Names()
- options := FilterList(names, args[0], "", aerc.SelectedAccountUiConfig().FuzzyComplete)
-
- if len(options) > 0 {
- return options
- }
- return nil
+ return
}
func GetFolders(aerc *widgets.Aerc, args []string) []string {
@@ -246,3 +305,19 @@ func hasUpper(s string) bool {
}
return false
}
+
+// splitCmd splits the command into arguments
+func splitCmd(cmd string) ([]string, error) {
+ args, err := shlex.Split(cmd)
+ if err != nil {
+ return nil, err
+ }
+ return args, nil
+}
+
+func escape(s string) string {
+ if strings.Contains(s, " ") {
+ return strings.ReplaceAll(s, " ", "\\ ")
+ }
+ return s
+}