diff --git a/gen-go-code.py b/gen-go-code.py index d084af392..f2d01de70 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -95,10 +95,7 @@ def generate_completions_for_kitty() -> None: print('func kitty(root *Command) {') # The kitty exe - print('k := root.add_command("kitty", "")') - print('k.First_arg_may_not_be_subcommand = true') - print('k.Completion_for_arg = complete_kitty') - print('k.Subcommand_must_be_first = true') + print('k := root.AddSubCommand(&Command{Name:"kitty", SubCommandIsOptional: true, ArgCompleter: complete_kitty, SubCommandMustBeFirst: true })') for opt in go_options_for_seq(parse_option_spec()[0]): print(opt.as_completion_option('k')) from kitty.config import option_names_for_completion @@ -361,7 +358,7 @@ func add_rc_global_opts(cmd *cli.Command) {{ def update_completion() -> None: orig = sys.stdout try: - with replace_if_needed('tools/cli/completion/kitty_generated.go') as f: + with replace_if_needed('tools/cli/completion-kitty_generated.go') as f: sys.stdout = f generate_completions_for_kitty() finally: diff --git a/kitty/cli.py b/kitty/cli.py index b03dd4a55..3edd1852f 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -73,12 +73,12 @@ class CompletionSpec: raise KeyError(f'Unknown completion property: {ck}') return self - def as_go_code(self, go_name: str, sep: str = ' = ') -> Iterator[str]: + def as_go_code(self, go_name: str, sep: str = ': ') -> Iterator[str]: completers = [] if self.kwds: kwds = (f'"{serialize_as_go_string(x)}"' for x in self.kwds) g = (self.group if self.type is CompletionType.keyword else '') or "Keywords" - completers.append(f'names_completer("{serialize_as_go_string(g)}", ' + ', '.join(kwds) + ')') + completers.append(f'NamesCompleter("{serialize_as_go_string(g)}", ' + ', '.join(kwds) + ')') relative_to = 'CONFIG' if self.relative_to is CompletionRelativeTo.config_dir else 'CWD' if self.type is CompletionType.file: g = serialize_as_go_string(self.group or 'Files') @@ -95,9 +95,9 @@ class CompletionSpec: if go_name: go_name += '.' if len(completers) > 1: - yield f'{go_name}Completion_for_arg{sep}chain_completers(' + ', '.join(completers) + ')' + yield f'{go_name}{sep}chain_completers(' + ', '.join(completers) + ')' elif completers: - yield f'{go_name}Completion_for_arg{sep}{completers[0]}' + yield f'{go_name}{sep}{completers[0]}' class OptionDict(TypedDict): @@ -159,7 +159,11 @@ class GoOption: ''' if self.type in ('choice', 'choices'): c = ', '.join(self.sorted_choices) + cx = ', '.join(f'"{serialize_as_go_string(x)}"' for x in self.sorted_choices) ans += f'\nChoices: "{serialize_as_go_string(c)}",\n' + ans += f'\nCompleter: NamesCompleter("Choices for {self.long}", {cx}),' + elif self.obj_dict['completion'].type is not CompletionType.none: + ans += ''.join(self.obj_dict['completion'].as_go_code('Completer', ': ')) + ',' if depth > 0: ans += f'\nDepth: {depth},\n' if self.default: @@ -173,20 +177,6 @@ class GoOption: choices.insert(0, self.default or '') return choices - def as_completion_option(self, command_name: str) -> str: - ans = f'{command_name}.add_option(&' 'Option{Name: ' f'"{serialize_as_go_string(self.long)}", ' - ans += f'Description: "{self.help_text}", ' - aliases = (f'"{serialize_as_go_string(x)}"' for x in self.aliases) - ans += 'Aliases: []string{' f'{", ".join(aliases)}' '}, ' - if self.go_type != 'bool': - ans += 'Has_following_arg: true, ' - if self.type in ('choices', 'choice'): - cx = ', '.join(f'"{serialize_as_go_string(x)}"' for x in self.sorted_choices) - ans += f'Completion_for_arg: names_completer("Choices for {self.long}", {cx}),' - elif self.obj_dict['completion'].type is not CompletionType.none: - ans += ''.join(self.obj_dict['completion'].as_go_code('', ': ')) + ',' - return ans + '})' - def go_options_for_seq(seq: 'OptionSpecSeq') -> Iterator[GoOption]: for x in seq: diff --git a/kitty/rc/base.py b/kitty/rc/base.py index 4bd7a41a9..9b49481b5 100644 --- a/kitty/rc/base.py +++ b/kitty/rc/base.py @@ -201,7 +201,7 @@ class ArgsHandling: def as_go_completion_code(self, go_name: str) -> Iterator[str]: c = self.args_count if c is not None: - yield f'{go_name}.Stop_processing_at_arg = {c}' + yield f'{go_name}.StopCompletingAtArg = {c}' if self.completion: yield from self.completion.as_go_code(go_name) diff --git a/tools/cli/completion/bash.go b/tools/cli/bash.go similarity index 98% rename from tools/cli/completion/bash.go rename to tools/cli/bash.go index 9ef7d0e53..f2665e4e8 100644 --- a/tools/cli/completion/bash.go +++ b/tools/cli/bash.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "fmt" diff --git a/tools/cli/command.go b/tools/cli/command.go index a17272cd9..642b76447 100644 --- a/tools/cli/command.go +++ b/tools/cli/command.go @@ -23,8 +23,16 @@ type Command struct { AllowOptionsAfterArgs int // If true does not fail if the first non-option arg is not a sub-command SubCommandIsOptional bool + // If true subcommands are ignored unless they are the first non-option argument + SubCommandMustBeFirst bool // The entry point for this command Run func(cmd *Command, args []string) (int, error) + // The completer for args + ArgCompleter CompletionFunc + // Stop completion processing at this arg num + StopCompletingAtArg int + // Specialised arg aprsing + ParseArgsForCompletion func(cmd *Command, args []string, completions *Completions) SubCommandGroups []*CommandGroup OptionGroups []*OptionGroup @@ -32,7 +40,8 @@ type Command struct { Args []string - option_map map[string]*Option + option_map map[string]*Option + index_of_first_arg int } func (self *Command) Clone(parent *Command) *Command { @@ -41,6 +50,7 @@ func (self *Command) Clone(parent *Command) *Command { ans.Parent = parent ans.SubCommandGroups = make([]*CommandGroup, len(self.SubCommandGroups)) ans.OptionGroups = make([]*OptionGroup, len(self.OptionGroups)) + ans.option_map = nil for i, o := range self.OptionGroups { ans.OptionGroups[i] = o.Clone(&ans) @@ -63,6 +73,7 @@ func init_cmd(c *Command) { c.SubCommandGroups = make([]*CommandGroup, 0, 8) c.OptionGroups = make([]*OptionGroup, 0, 8) c.Args = make([]string, 0, 8) + c.option_map = nil } func NewRootCommand() *Command { @@ -244,6 +255,12 @@ func (self *Command) VisitAllOptions(callback func(*Option) error) error { return nil } +func (self *Command) AllOptions() []*Option { + ans := make([]*Option, 0, 64) + self.VisitAllOptions(func(o *Option) error { ans = append(ans, o); return nil }) + return ans +} + func (self *Command) GetVisibleOptions() ([]string, map[string][]*Option) { group_titles := make([]string, 0, len(self.OptionGroups)) gmap := make(map[string][]*Option) @@ -453,3 +470,29 @@ func (self *Command) Exec(args ...string) { } os.Exit(exit_code) } + +func (self *Command) GetCompletions(argv []string, init_completions func(*Completions)) *Completions { + ans := Completions{Groups: make([]*MatchGroup, 0, 4)} + if init_completions != nil { + init_completions(&ans) + } + if len(argv) > 0 { + exe := argv[0] + cmd := self.FindSubCommand(exe) + if cmd != nil { + if cmd.ParseArgsForCompletion != nil { + cmd.ParseArgsForCompletion(cmd, argv[1:], &ans) + } else { + completion_parse_args(cmd, argv[1:], &ans) + } + } + } + non_empty_groups := make([]*MatchGroup, 0, len(ans.Groups)) + for _, gr := range ans.Groups { + if len(gr.Matches) > 0 { + non_empty_groups = append(non_empty_groups, gr) + } + } + ans.Groups = non_empty_groups + return &ans +} diff --git a/tools/cli/completion/kitty.go b/tools/cli/completion-kitty.go similarity index 99% rename from tools/cli/completion/kitty.go rename to tools/cli/completion-kitty.go index 6c01f7a17..3a3c6d769 100644 --- a/tools/cli/completion/kitty.go +++ b/tools/cli/completion-kitty.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "bufio" diff --git a/tools/cli/completion/main.go b/tools/cli/completion-main.go similarity index 95% rename from tools/cli/completion/main.go rename to tools/cli/completion-main.go index b28dee463..fbcf7e55b 100644 --- a/tools/cli/completion/main.go +++ b/tools/cli/completion-main.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "encoding/json" @@ -76,9 +76,9 @@ func Main(args []string) error { if err != nil { return err } - var root = Command{Options: make([]*Option, 0), Groups: make([]*CommandGroup, 0, 8)} + var root = NewRootCommand() for _, re := range registered_exes { - re(&root) + re(root) } all_completions := make([]*Completions, 0, 1) diff --git a/tools/cli/completion/parse-args.go b/tools/cli/completion-parse-args.go similarity index 61% rename from tools/cli/completion/parse-args.go rename to tools/cli/completion-parse-args.go index e61f1535d..be3a249b7 100644 --- a/tools/cli/completion/parse-args.go +++ b/tools/cli/completion-parse-args.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "fmt" @@ -17,36 +17,16 @@ func (self *Completions) add_group(group *MatchGroup) { } } -func (self *Command) find_option(name_including_leading_dash string) *Option { - var q string - if strings.HasPrefix(name_including_leading_dash, "--") { - q = name_including_leading_dash[2:] - } else if strings.HasPrefix(name_including_leading_dash, "-") { - q = name_including_leading_dash[len(name_including_leading_dash)-1:] - } else { - q = 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 := self.add_match_group("Options") if strings.HasPrefix(word, "--") { if word == "--" { group.Matches = append(group.Matches, &Match{Word: "--", Description: "End of options"}) } - prefix := word[2:] for _, opt := range options { for _, q := range opt.Aliases { - if len(q) > 1 && strings.HasPrefix(q, prefix) { - group.Matches = append(group.Matches, &Match{Word: "--" + q, Description: opt.Description}) + if strings.HasPrefix(q.String(), word) { + group.Matches = append(group.Matches, &Match{Word: q.String(), Description: opt.Help}) break } } @@ -57,14 +37,19 @@ func (self *Completions) add_options_group(options []*Option, word string) { for _, opt := range options { has_single_letter_alias := false for _, q := range opt.Aliases { - if len(q) == 1 { - group.add_match("-"+q, opt.Description) + if q.IsShort { + group.add_match("-"+q.NameWithoutHyphens, opt.Help) has_single_letter_alias = true break } } if !has_single_letter_alias { - group.add_match("--"+opt.Aliases[0], opt.Description) + for _, q := range opt.Aliases { + if !q.IsShort { + group.add_match(q.String(), opt.Help) + break + } + } } } } else { @@ -72,8 +57,8 @@ func (self *Completions) add_options_group(options []*Option, word string) { last_letter := string(runes[len(runes)-1]) for _, opt := range options { for _, q := range opt.Aliases { - if q == last_letter { - group.add_match(word, opt.Description) + if q.IsShort && q.NameWithoutHyphens == last_letter { + group.add_match(word, opt.Help) return } } @@ -82,54 +67,61 @@ func (self *Completions) add_options_group(options []*Option, word string) { } } +func (self *Command) sub_command_allowed_at(completions *Completions, arg_num int) bool { + if self.SubCommandMustBeFirst { + return arg_num == 1 && completions.current_word_idx_in_parent == 1 + } + return arg_num == 1 +} + 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 { - expecting_arg_for.Completion_for_arg(completions, word, arg_num) + if expecting_arg_for.Completer != nil { + expecting_arg_for.Completer(completions, word, arg_num) } return } if !only_args_allowed && strings.HasPrefix(word, "-") { if strings.HasPrefix(word, "--") && strings.Contains(word, "=") { idx := strings.Index(word, "=") - option := cmd.find_option(word[:idx]) + option := cmd.FindOption(word[:idx]) if option != nil { - if option.Completion_for_arg != nil { - option.Completion_for_arg(completions, word[idx+1:], arg_num) + if option.Completer != nil { + option.Completer(completions, word[idx+1:], arg_num) completions.add_prefix_to_all_matches(word[:idx+1]) } } } else { - completions.add_options_group(cmd.Options, word) + completions.add_options_group(cmd.AllOptions(), word) } return } - if cmd.has_subcommands() && cmd.sub_command_allowed_at(completions, arg_num) { - for _, cg := range cmd.Groups { + if cmd.HasVisibleSubCommands() && cmd.sub_command_allowed_at(completions, arg_num) { + for _, cg := range cmd.SubCommandGroups { group := completions.add_match_group(cg.Title) if group.Title == "" { group.Title = "Sub-commands" } - for _, sc := range cg.Commands { + for _, sc := range cg.SubCommands { if strings.HasPrefix(sc.Name, word) { - group.add_match(sc.Name, sc.Description) + group.add_match(sc.Name, sc.HelpText) } } } - if cmd.First_arg_may_not_be_subcommand && cmd.Completion_for_arg != nil { - cmd.Completion_for_arg(completions, word, arg_num) + if !cmd.SubCommandMustBeFirst && cmd.ArgCompleter != nil { + cmd.ArgCompleter(completions, word, arg_num) } return } - if cmd.Completion_for_arg != nil { - cmd.Completion_for_arg(completions, word, arg_num) + if cmd.ArgCompleter != nil { + cmd.ArgCompleter(completions, word, arg_num) } return } -func default_parse_args(cmd *Command, words []string, completions *Completions) { +func completion_parse_args(cmd *Command, words []string, completions *Completions) { completions.current_cmd = cmd if len(words) == 0 { complete_word("", completions, false, nil, 0) @@ -174,15 +166,15 @@ func default_parse_args(cmd *Command, words []string, completions *Completions) } if !only_args_allowed && strings.HasPrefix(word, "-") { if !strings.Contains(word, "=") { - option := cmd.find_option(word) - if option != nil && option.Has_following_arg { + option := cmd.FindOption(word) + if option != nil && option.needs_argument() { expecting_arg_for = option } } continue } - if cmd.has_subcommands() && cmd.sub_command_allowed_at(completions, arg_num) { - sc := cmd.find_subcommand_with_name(word) + if cmd.HasVisibleSubCommands() && cmd.sub_command_allowed_at(completions, arg_num) { + sc := cmd.FindSubCommand(word) if sc == nil { only_args_allowed = true continue @@ -192,11 +184,11 @@ func default_parse_args(cmd *Command, words []string, completions *Completions) arg_num = 0 completions.current_word_idx_in_parent = 0 only_args_allowed = false - if cmd.Parse_args != nil { - cmd.Parse_args(cmd, words[i+1:], completions) + if cmd.ParseArgsForCompletion != nil { + cmd.ParseArgsForCompletion(cmd, words[i+1:], completions) return } - } else if cmd.Stop_processing_at_arg > 0 && arg_num >= cmd.Stop_processing_at_arg { + } else if cmd.StopCompletingAtArg > 0 && arg_num >= cmd.StopCompletingAtArg { return } else { only_args_allowed = true diff --git a/tools/cli/completion.go b/tools/cli/completion.go new file mode 100644 index 000000000..d3c46e259 --- /dev/null +++ b/tools/cli/completion.go @@ -0,0 +1,154 @@ +// License: GPLv3 Copyright: 2022, Kovid Goyal, + +package cli + +import ( + "fmt" + "path/filepath" + "strings" + + "kitty/tools/utils" + "kitty/tools/wcswidth" +) + +var _ = fmt.Print + +type Match struct { + Word string `json:"word,omitempty"` + Description string `json:"description,omitempty"` +} + +type MatchGroup struct { + Title string `json:"title,omitempty"` + NoTrailingSpace bool `json:"no_trailing_space,omitempty"` + IsFiles bool `json:"is_files,omitempty"` + Matches []*Match `json:"matches,omitempty"` +} + +func (self *MatchGroup) remove_common_prefix() string { + if self.IsFiles { + if len(self.Matches) > 1 { + lcp := self.longest_common_prefix() + if strings.Contains(lcp, utils.Sep) { + lcp = strings.TrimRight(filepath.Dir(lcp), utils.Sep) + utils.Sep + self.remove_prefix_from_all_matches(lcp) + return lcp + } + } + } else if len(self.Matches) > 1 && strings.HasPrefix(self.Matches[0].Word, "--") && strings.Contains(self.Matches[0].Word, "=") { + lcp, _, _ := utils.Cut(self.longest_common_prefix(), "=") + lcp += "=" + if len(lcp) > 3 { + self.remove_prefix_from_all_matches(lcp) + return lcp + } + } + return "" +} + +func (self *MatchGroup) add_match(word string, description ...string) *Match { + ans := Match{Word: word, Description: strings.Join(description, " ")} + self.Matches = append(self.Matches, &ans) + return &ans +} + +func (self *MatchGroup) add_prefix_to_all_matches(prefix string) { + for _, m := range self.Matches { + m.Word = prefix + m.Word + } +} + +func (self *MatchGroup) remove_prefix_from_all_matches(prefix string) { + for _, m := range self.Matches { + m.Word = m.Word[len(prefix):] + } +} + +func (self *MatchGroup) has_descriptions() bool { + for _, m := range self.Matches { + if m.Description != "" { + return true + } + } + return false +} + +func (self *MatchGroup) max_visual_word_length(limit int) int { + ans := 0 + for _, m := range self.Matches { + if q := wcswidth.Stringwidth(m.Word); q > ans { + ans = q + if ans > limit { + return limit + } + } + } + return ans +} + +func (self *MatchGroup) longest_common_prefix() string { + limit := len(self.Matches) + i := 0 + return utils.LongestCommon(func() (string, bool) { + if i < limit { + i++ + return self.Matches[i-1].Word, false + } + return "", true + }, true) +} + +type Delegate struct { + NumToRemove int `json:"num_to_remove,omitempty"` + Command string `json:"command,omitempty"` +} + +type Completions struct { + Groups []*MatchGroup `json:"groups,omitempty"` + Delegate Delegate `json:"delegate,omitempty"` + + current_cmd *Command + all_words []string // all words passed to parse_args() + current_word_idx int // index of current word in all_words + current_word_idx_in_parent int // index of current word in parents command line 1 for first word after parent + + split_on_equals bool // true if the cmdline is split on = (BASH does this because readline does this) +} + +func (self *Completions) add_prefix_to_all_matches(prefix string) { + for _, mg := range self.Groups { + mg.add_prefix_to_all_matches(prefix) + } +} + +func (self *Completions) add_match_group(title string) *MatchGroup { + for _, q := range self.Groups { + if q.Title == title { + return q + } + } + ans := MatchGroup{Title: title, Matches: make([]*Match, 0, 8)} + self.Groups = append(self.Groups, &ans) + return &ans +} + +type CompletionFunc func(completions *Completions, word string, arg_num int) + +func NamesCompleter(title string, names ...string) CompletionFunc { + return func(completions *Completions, word string, arg_num int) { + mg := completions.add_match_group(title) + for _, q := range names { + if strings.HasPrefix(q, word) { + mg.add_match(q) + } + } + } +} + +func ChainCompleters(completers ...CompletionFunc) CompletionFunc { + return func(completions *Completions, word string, arg_num int) { + for _, f := range completers { + f(completions, word, arg_num) + } + } +} diff --git a/tools/cli/completion/types.go b/tools/cli/completion/types.go index 87041caef..96a3473ae 100644 --- a/tools/cli/completion/types.go +++ b/tools/cli/completion/types.go @@ -9,127 +9,6 @@ import ( "strings" ) -type Match struct { - Word string `json:"word,omitempty"` - Description string `json:"description,omitempty"` -} - -type MatchGroup struct { - Title string `json:"title,omitempty"` - NoTrailingSpace bool `json:"no_trailing_space,omitempty"` - IsFiles bool `json:"is_files,omitempty"` - Matches []*Match `json:"matches,omitempty"` -} - -func (self *MatchGroup) remove_common_prefix() string { - if self.IsFiles { - if len(self.Matches) > 1 { - lcp := self.longest_common_prefix() - if strings.Contains(lcp, utils.Sep) { - lcp = strings.TrimRight(filepath.Dir(lcp), utils.Sep) + utils.Sep - self.remove_prefix_from_all_matches(lcp) - return lcp - } - } - } else if len(self.Matches) > 1 && strings.HasPrefix(self.Matches[0].Word, "--") && strings.Contains(self.Matches[0].Word, "=") { - lcp, _, _ := utils.Cut(self.longest_common_prefix(), "=") - lcp += "=" - if len(lcp) > 3 { - self.remove_prefix_from_all_matches(lcp) - return lcp - } - } - return "" -} - -func (self *MatchGroup) add_match(word string, description ...string) *Match { - ans := Match{Word: word, Description: strings.Join(description, " ")} - self.Matches = append(self.Matches, &ans) - return &ans -} - -func (self *MatchGroup) add_prefix_to_all_matches(prefix string) { - for _, m := range self.Matches { - m.Word = prefix + m.Word - } -} - -func (self *MatchGroup) remove_prefix_from_all_matches(prefix string) { - for _, m := range self.Matches { - m.Word = m.Word[len(prefix):] - } -} - -func (self *MatchGroup) has_descriptions() bool { - for _, m := range self.Matches { - if m.Description != "" { - return true - } - } - return false -} - -func (self *MatchGroup) max_visual_word_length(limit int) int { - ans := 0 - for _, m := range self.Matches { - if q := wcswidth.Stringwidth(m.Word); q > ans { - ans = q - if ans > limit { - return limit - } - } - } - return ans -} - -func (self *MatchGroup) longest_common_prefix() string { - limit := len(self.Matches) - i := 0 - return utils.LongestCommon(func() (string, bool) { - if i < limit { - i++ - return self.Matches[i-1].Word, false - } - return "", true - }, true) -} - -type Delegate struct { - NumToRemove int `json:"num_to_remove,omitempty"` - Command string `json:"command,omitempty"` -} - -type Completions struct { - Groups []*MatchGroup `json:"groups,omitempty"` - Delegate Delegate `json:"delegate,omitempty"` - - current_cmd *Command - all_words []string // all words passed to parse_args() - current_word_idx int // index of current word in all_words - current_word_idx_in_parent int // index of current word in parents command line 1 for first word after parent - - split_on_equals bool // true if the cmdline is split on = (BASH does this because readline does this) -} - -func (self *Completions) add_prefix_to_all_matches(prefix string) { - for _, mg := range self.Groups { - mg.add_prefix_to_all_matches(prefix) - } -} - -func (self *Completions) add_match_group(title string) *MatchGroup { - for _, q := range self.Groups { - if q.Title == title { - return q - } - } - ans := MatchGroup{Title: title, Matches: make([]*Match, 0, 8)} - self.Groups = append(self.Groups, &ans) - return &ans -} - -type CompletionFunc func(completions *Completions, word string, arg_num int) - type Option struct { Name string Aliases []string diff --git a/tools/cli/completion/files.go b/tools/cli/files.go similarity index 94% rename from tools/cli/completion/files.go rename to tools/cli/files.go index 3f612528d..252aea4ec 100644 --- a/tools/cli/completion/files.go +++ b/tools/cli/files.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "fmt" @@ -114,7 +114,7 @@ func complete_files(prefix string, callback func(*FileEntry), cwd string) error return nil } -func complete_executables_in_path(prefix string, paths ...string) []string { +func CompleteExecutablesInPath(prefix string, paths ...string) []string { ans := make([]string, 0, 1024) if len(paths) == 0 { paths = filepath.SplitList(os.Getenv("PATH")) @@ -253,15 +253,15 @@ func make_completer(title string, relative_to relative_to, patterns []string, f } } -func fnmatch_completer(title string, relative_to relative_to, patterns ...string) CompletionFunc { +func FnmatchCompleter(title string, relative_to relative_to, patterns ...string) CompletionFunc { return make_completer(title, relative_to, patterns, complete_by_fnmatch) } -func mimepat_completer(title string, relative_to relative_to, patterns ...string) CompletionFunc { +func MimepatCompleter(title string, relative_to relative_to, patterns ...string) CompletionFunc { return make_completer(title, relative_to, patterns, complete_by_mimepat) } -func directory_completer(title string, relative_to relative_to) CompletionFunc { +func DirectoryCompleter(title string, relative_to relative_to) CompletionFunc { if title == "" { title = "Directories" } diff --git a/tools/cli/completion/files_test.go b/tools/cli/files_test.go similarity index 98% rename from tools/cli/completion/files_test.go rename to tools/cli/files_test.go index 0d6589f27..e52650ff6 100644 --- a/tools/cli/completion/files_test.go +++ b/tools/cli/files_test.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "fmt" @@ -112,7 +112,7 @@ func TestCompleteExecutables(t *testing.T) { if expected == nil { expected = make([]string, 0) } - actual := complete_executables_in_path(prefix) + actual := CompleteExecutablesInPath(prefix) sort.Strings(expected) sort.Strings(actual) if !reflect.DeepEqual(expected, actual) { diff --git a/tools/cli/completion/fish.go b/tools/cli/fish.go similarity index 98% rename from tools/cli/completion/fish.go rename to tools/cli/fish.go index c1d7cc53e..ce434170f 100644 --- a/tools/cli/completion/fish.go +++ b/tools/cli/fish.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "fmt" diff --git a/tools/cli/option-from-string.go b/tools/cli/option-from-string.go index c08380b47..ea9c61433 100644 --- a/tools/cli/option-from-string.go +++ b/tools/cli/option-from-string.go @@ -176,7 +176,7 @@ func option_from_spec(spec OptionSpec) (*Option, error) { if ans.IsList { ans.parsed_default = []string{} } - ans.CompletionFunc = spec.CompletionFunc + ans.Completer = spec.Completer if ans.Aliases == nil || len(ans.Aliases) == 0 { return nil, fmt.Errorf("No --aliases specified for option") } diff --git a/tools/cli/option.go b/tools/cli/option.go index f14e0ab9e..75da94707 100644 --- a/tools/cli/option.go +++ b/tools/cli/option.go @@ -8,8 +8,6 @@ import ( "strings" "golang.org/x/exp/slices" - - "kitty/tools/cli/completion" ) var _ = fmt.Print @@ -38,28 +36,28 @@ func (self *Alias) String() string { } type OptionSpec struct { - Name string - Type string - Dest string - Choices string - Depth int - Default string - Help string - CompletionFunc *completion.CompletionFunc + Name string + Type string + Dest string + Choices string + Depth int + Default string + Help string + Completer CompletionFunc } type Option struct { - Name string - Aliases []Alias - Choices []string - Default string - OptionType OptionType - Hidden bool - Depth int - Help string - IsList bool - Parent *Command - CompletionFunc *completion.CompletionFunc + Name string + Aliases []Alias + Choices []string + Default string + OptionType OptionType + Hidden bool + Depth int + Help string + IsList bool + Parent *Command + Completer CompletionFunc values_from_cmdline []string parsed_values_from_cmdline []any diff --git a/tools/cli/completion/zsh.go b/tools/cli/zsh.go similarity index 99% rename from tools/cli/completion/zsh.go rename to tools/cli/zsh.go index 240076da4..917d69d33 100644 --- a/tools/cli/completion/zsh.go +++ b/tools/cli/zsh.go @@ -1,6 +1,6 @@ // License: GPLv3 Copyright: 2022, Kovid Goyal, -package completion +package cli import ( "bufio"