diff --git a/gen-go-code.py b/gen-go-code.py index 133835bcd..31be6cf26 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -3,6 +3,7 @@ import io import json +import sys from contextlib import contextmanager, suppress from typing import Dict, Iterator, List, Set, Tuple, Union @@ -45,6 +46,17 @@ def replace(template: str, **kw: str) -> str: # }}} +def generate_completions_for_kitty() -> None: + from kitty.entry_points import entry_points, namespaced_entry_points + print('package completion\n') + print('func kitty(root *Command) {') + print('k := root.add_command("kitty")') + print('}') + print('func init() {') + print('registered_exes["kitty"] = kitty') + print('}') + + # rc command wrappers {{{ json_field_types: Dict[str, str] = { 'bool': 'bool', 'str': 'string', 'list.str': '[]string', 'dict.str': 'map[string]string', 'float': 'float64', 'int': 'int', @@ -247,7 +259,13 @@ def update_at_commands() -> None: def update_completion() -> None: - pass + orig = sys.stdout + try: + with replace_if_needed('tools/completion/kitty_generated.go') as f: + sys.stdout = f + generate_completions_for_kitty() + finally: + sys.stdout = orig def main() -> None: diff --git a/tools/completion/main.go b/tools/completion/main.go index 058cc7ff6..f66b9e551 100644 --- a/tools/completion/main.go +++ b/tools/completion/main.go @@ -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) diff --git a/tools/completion/parse-args.go b/tools/completion/parse-args.go index 756206f79..1d30a932c 100644 --- a/tools/completion/parse-args.go +++ b/tools/completion/parse-args.go @@ -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 diff --git a/tools/completion/types.go b/tools/completion/types.go index eb2da2b44..19a4ef275 100644 --- a/tools/completion/types.go +++ b/tools/completion/types.go @@ -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) }