More work on completions

This commit is contained in:
Kovid Goyal
2022-09-07 10:55:07 +05:30
parent d0efe00449
commit 63287e4115
4 changed files with 82 additions and 36 deletions

View File

@@ -35,6 +35,8 @@ func init() {
output_serializers["json"] = json_output_serializer
}
var registered_exes = make(map[string]func(root *Command))
func main(args []string) error {
output_type := "json"
if len(args) > 0 {
@@ -62,7 +64,12 @@ func main(args []string) error {
if err != nil {
return err
}
completions := GetCompletions(argv)
var root = Command{Options: make([]*Option, 0), Groups: make([]*CommandGroup, 0, 8)}
for _, re := range registered_exes {
re(&root)
}
completions := root.GetCompletions(argv)
output, err := output_serializer(completions, shell_state)
if err == nil {
_, err = os.Stdout.Write(output)

View File

@@ -70,7 +70,7 @@ func complete_word(word string, completions *Completions, only_args_allowed bool
cmd := completions.current_cmd
if expecting_arg_for != nil {
if expecting_arg_for.Completion_for_arg != nil {
expecting_arg_for.Completion_for_arg(completions, word)
expecting_arg_for.Completion_for_arg(completions, word, arg_num)
}
return
}
@@ -81,7 +81,7 @@ func complete_word(word string, completions *Completions, only_args_allowed bool
if option != nil {
if option.Completion_for_arg != nil {
completions.WordPrefix = word[:idx+1]
option.Completion_for_arg(completions, word[idx+1:])
option.Completion_for_arg(completions, word[idx+1:], arg_num)
}
}
} else {
@@ -89,18 +89,19 @@ func complete_word(word string, completions *Completions, only_args_allowed bool
}
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 arg_num == 1 && cmd.has_subcommands() {
for _, cg := range cmd.Groups {
group := MatchGroup{Title: cg.Title}
if group.Title == "" {
group.Title = "Sub-commands"
}
group.Matches = make([]*Match, 0, len(cg.Commands))
for _, sc := range cg.Commands {
if strings.HasPrefix(sc.Name, word) {
group.Matches = append(group.Matches, &Match{Word: sc.Name, Description: sc.Description})
}
}
if len(group.Matches) > 0 {
completions.add_group(&group)
}
}
@@ -108,20 +109,11 @@ func complete_word(word string, completions *Completions, only_args_allowed bool
}
if cmd.Completion_for_arg != nil {
cmd.Completion_for_arg(completions, word)
cmd.Completion_for_arg(completions, word, arg_num)
}
return
}
func (self *Command) find_subcommand(name string) *Command {
for _, sc := range self.Subcommands {
if sc.Name == name {
return sc
}
}
return nil
}
func (cmd *Command) parse_args(words []string, completions *Completions) {
completions.current_cmd = cmd
if len(words) == 0 {
@@ -156,13 +148,13 @@ func (cmd *Command) parse_args(words []string, completions *Completions) {
continue
}
option := cmd.find_option(word[:idx])
if option != nil {
if option != nil && option.Has_following_arg {
expecting_arg_for = option
}
continue
}
if len(cmd.Subcommands) > 0 && arg_num == 1 {
sc := cmd.find_subcommand(word)
if cmd.has_subcommands() && arg_num == 1 {
sc := cmd.find_subcommand_with_name(word)
if sc == nil {
only_args_allowed = true
continue

View File

@@ -23,7 +23,7 @@ type Completions struct {
current_cmd *Command
}
type completion_func func(completions *Completions, partial_word string)
type completion_func func(completions *Completions, partial_word string, arg_num int)
type Option struct {
Name string
@@ -33,29 +33,58 @@ type Option struct {
Completion_for_arg completion_func
}
type CommandGroup struct {
Title string
Commands []*Command
}
type Command struct {
Name string
Description string
// List of options for this command
Options []*Option
// List of subcommands
Subcommands []*Command
// Optional title used as a header when displaying the list of matching sub-commands for a completion
Subcommands_title string
Groups []*CommandGroup
Completion_for_arg completion_func
Stop_processing_at_arg int
}
var Root = Command{Options: make([]*Option, 0), Subcommands: make([]*Command, 0, 32)}
func (self *Command) add_command(name string) *Command {
ans := Command{Name: name}
ans.Options = make([]*Option, 0, 8)
ans.Groups = make([]*CommandGroup, 0, 2)
return &ans
}
func GetCompletions(argv []string) *Completions {
func (self *Command) find_subcommand(is_ok func(cmd *Command) bool) *Command {
for _, g := range self.Groups {
for _, q := range g.Commands {
if is_ok(q) {
return q
}
}
}
return nil
}
func (self *Command) find_subcommand_with_name(name string) *Command {
return self.find_subcommand(func(cmd *Command) bool { return cmd.Name == name })
}
func (self *Command) has_subcommands() bool {
for _, g := range self.Groups {
if len(g.Commands) > 0 {
return true
}
}
return false
}
func (self *Command) GetCompletions(argv []string) *Completions {
ans := Completions{Groups: make([]*MatchGroup, 0, 4)}
if len(argv) > 0 {
exe := argv[0]
cmd := Root.find_subcommand(exe)
cmd := self.find_subcommand_with_name(exe)
if cmd != nil {
cmd.parse_args(argv[1:], &ans)
}