From f4de6d2a10dca3e08e3113a830670d790f48e168 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 5 Sep 2022 21:43:16 +0530 Subject: [PATCH] More work on completions --- tools/completion/parse-args.go | 99 ++++++++++++++++++++++++++++++---- tools/completion/types.go | 22 +++++--- 2 files changed, 105 insertions(+), 16 deletions(-) diff --git a/tools/completion/parse-args.go b/tools/completion/parse-args.go index ac94949a0..2fe4213ef 100644 --- a/tools/completion/parse-args.go +++ b/tools/completion/parse-args.go @@ -6,7 +6,60 @@ import ( "strings" ) -func complete_word(word string, completions *Completions, only_args_allowed bool, expecting_arg_for *option) { +func (self *Completions) add_group(group *MatchGroup) { + if len(group.Matches) > 0 { + self.Groups = append(self.Groups, group) + } +} + +func (self *command) find_option(name_including_leading_dash string) *option { + q := strings.TrimLeft(name_including_leading_dash, "-") + for _, opt := range self.options { + for _, alias := range opt.aliases { + if alias == q { + return &opt + } + } + } + return nil +} + +func (self *Completions) add_options_group(options []option, word string) { + group := MatchGroup{Title: "Options"} + group.Matches = make([]*Match, 0, 8) + seen_flags := make(map[string]bool) + if strings.HasPrefix(word, "--") { + prefix := word[2:] + for _, opt := range options { + for _, q := range opt.aliases { + if len(q) > 1 && strings.HasPrefix(q, prefix) { + seen_flags[q] = true + group.Matches = append(group.Matches, &Match{Word: "--" + q, Description: opt.description}) + } + } + } + } else { + if word == "-" { + group.Matches = append(group.Matches, &Match{Word: "--"}) + } else { + for _, letter := range []rune(word[1:]) { + seen_flags[string(letter)] = true + } + } + group.WordPrefix = word + for _, opt := range options { + for _, q := range opt.aliases { + if len(q) == 1 && !seen_flags[q] { + seen_flags[q] = true + group.Matches = append(group.Matches, &Match{Word: q, FullForm: "-" + q, Description: opt.description}) + } + } + } + } + self.add_group(&group) +} + +func complete_word(word string, completions *Completions, only_args_allowed bool, expecting_arg_for *option, arg_num int) { cmd := Completions.current_cmd if expecting_arg_for != nil { if expecting_arg_for.completion_for_arg != nil { @@ -15,13 +68,38 @@ func complete_word(word string, completions *Completions, only_args_allowed bool return } if !only_args_allowed && strings.HasPrefix(word, "-") { - // handle single letter multiple options -abcd - // handle standalone --long-opt - // handle long opt ends with = - // handle long opt containing = - completions.add_options_group(cmd.options, word) + if strings.HasPrefix(word, "--") && strings.Contains(word, "=") { + idx := strings.Index(word, "=") + option := cmd.find_option(word[:idx]) + if option != nil { + if option.completion_for_arg != nil { + completions.WordPrefix = word[:idx+1] + option.completion_for_arg(completions, word[idx+1:]) + } + } + } else { + completions.add_options_group(cmd.options, word) + } return } + if arg_num == 1 && len(cmd.subcommands) > 0 { + for _, sc := range cmd.subcommands { + if strings.HasPrefix(sc.name, word) { + title := cmd.subcommands_title + if title == "" { + title = "Sub-commands" + } + group := MatchGroup{Title: title} + group.Matches = make([]*Match, 0, len(cmd.subcommands)) + if strings.HasPrefix(sc, word) { + group.Matches = append(group.Matches, &Match{Word: sc.name, Description: sc.description}) + } + completions.add_group(&group) + } + } + return + } + if cmd.completion_for_arg != nil { cmd.completion_for_arg(completions, word) } @@ -31,7 +109,7 @@ func complete_word(word string, completions *Completions, only_args_allowed bool func parse_args(cmd *command, words []string, completions *Completions) { completions.current_cmd = cmd if len(words) == 0 { - complete_word("", completions, false, nil) + complete_word("", completions, false, nil, 0) return } @@ -42,8 +120,11 @@ func parse_args(cmd *command, words []string, completions *Completions) { for i, word := range words { cmd = completions.current_cmd is_last_word := i == len(words)-1 + if expecting_arg_for == nil && word != "--" { + arg_num++ + } if is_last_word { - complete_word(word, completions, only_args_allowed, expecting_arg_for) + complete_word(word, completions, only_args_allowed, expecting_arg_for, arg_num) } else { if expecting_arg_for != nil { expecting_arg_for = nil @@ -53,8 +134,8 @@ func parse_args(cmd *command, words []string, completions *Completions) { only_args_allowed = true continue } - arg_num++ if !only_args_allowed && strings.HasPrefix(word, "-") { + // TODO: // handle single letter multiple options -abcd // handle standalone --long-opt // handle long opt ends with = diff --git a/tools/completion/types.go b/tools/completion/types.go index 434408e85..d24bd0394 100644 --- a/tools/completion/types.go +++ b/tools/completion/types.go @@ -3,18 +3,21 @@ package completion type Match struct { - word string - description string + Word string + FullForm string + Description string } type MatchGroup struct { Title string NoTrailingSpace, IsFiles bool - Matches []Match + Matches []*Match + WordPrefix string } type Completions struct { - Groups MatchGroup + Groups []*MatchGroup + WordPrefix string current_cmd *command } @@ -30,9 +33,14 @@ type option struct { } type command struct { - options []option - subcommands []command - subcommands_title string + name string + description string + + options []option + + subcommands []command + subcommands_title string + completion_for_arg completion_func stop_processing_at_arg int }